init, works
This commit is contained in:
commit
78bb35fd9c
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
.directory
|
||||||
4
.idea/encodings.xml
generated
Normal file
4
.idea/encodings.xml
generated
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Encoding" addBOMForNewFiles="with NO BOM" />
|
||||||
|
</project>
|
||||||
10
.idea/libraries/MicroPython.xml
generated
Normal file
10
.idea/libraries/MicroPython.xml
generated
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<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/esp8266" />
|
||||||
|
</CLASSES>
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</component>
|
||||||
23
.idea/lopy_beacon.iml
generated
Normal file
23
.idea/lopy_beacon.iml
generated
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="PYTHON_MODULE" version="4">
|
||||||
|
<component name="FacetManager">
|
||||||
|
<facet type="MicroPython" name="MicroPython">
|
||||||
|
<configuration>
|
||||||
|
<device name="ESP8266" />
|
||||||
|
</configuration>
|
||||||
|
</facet>
|
||||||
|
</component>
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.idea" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/venv" />
|
||||||
|
<excludePattern pattern=".directory" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="jdk" jdkName="Python 3.6 (lopy_beacon)" jdkType="Python SDK" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
<orderEntry type="library" name="MicroPython" level="project" />
|
||||||
|
</component>
|
||||||
|
<component name="TestRunnerService">
|
||||||
|
<option name="PROJECT_TEST_RUNNER" value="Unittests" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
4
.idea/misc.xml
generated
Normal file
4
.idea/misc.xml
generated
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.6 (lopy_beacon)" project-jdk-type="Python SDK" />
|
||||||
|
</project>
|
||||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/lopy_beacon.iml" filepath="$PROJECT_DIR$/.idea/lopy_beacon.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
226
.idea/workspace.xml
generated
Normal file
226
.idea/workspace.xml
generated
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ChangeListManager">
|
||||||
|
<list default="true" id="4e6029b8-a0fe-4700-9dd8-e219ffe8ed9c" name="Default" comment="" />
|
||||||
|
<option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
|
||||||
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
|
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||||
|
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||||
|
</component>
|
||||||
|
<component name="FileEditorManager">
|
||||||
|
<leaf SIDE_TABS_SIZE_LIMIT_KEY="300">
|
||||||
|
<file pinned="false" current-in-tab="true">
|
||||||
|
<entry file="file://$PROJECT_DIR$/main.py">
|
||||||
|
<provider selected="true" editor-type-id="text-editor">
|
||||||
|
<state relative-caret-position="336">
|
||||||
|
<caret line="123" column="13" lean-forward="true" selection-start-line="123" selection-start-column="13" selection-end-line="123" selection-end-column="13" />
|
||||||
|
<folding>
|
||||||
|
<element signature="e#86#110#0" expanded="true" />
|
||||||
|
</folding>
|
||||||
|
</state>
|
||||||
|
</provider>
|
||||||
|
</entry>
|
||||||
|
</file>
|
||||||
|
<file pinned="false" current-in-tab="false">
|
||||||
|
<entry file="file://$PROJECT_DIR$/config.py">
|
||||||
|
<provider selected="true" editor-type-id="text-editor">
|
||||||
|
<state relative-caret-position="68">
|
||||||
|
<caret line="4" selection-start-line="4" selection-end-line="4" />
|
||||||
|
<folding>
|
||||||
|
<element signature="e#57#71#0" expanded="true" />
|
||||||
|
</folding>
|
||||||
|
</state>
|
||||||
|
</provider>
|
||||||
|
</entry>
|
||||||
|
</file>
|
||||||
|
<file pinned="false" current-in-tab="false">
|
||||||
|
<entry file="file://$PROJECT_DIR$/dth.py">
|
||||||
|
<provider selected="true" editor-type-id="text-editor">
|
||||||
|
<state relative-caret-position="629">
|
||||||
|
<caret line="40" column="48" lean-forward="true" selection-start-line="40" selection-start-column="48" selection-end-line="40" selection-end-column="48" />
|
||||||
|
<folding>
|
||||||
|
<element signature="e#0#11#0" expanded="true" />
|
||||||
|
</folding>
|
||||||
|
</state>
|
||||||
|
</provider>
|
||||||
|
</entry>
|
||||||
|
</file>
|
||||||
|
</leaf>
|
||||||
|
</component>
|
||||||
|
<component name="FileTemplateManagerImpl">
|
||||||
|
<option name="RECENT_TEMPLATES">
|
||||||
|
<list>
|
||||||
|
<option value="Python Script" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="FindInProjectRecents">
|
||||||
|
<findStrings>
|
||||||
|
<find>join</find>
|
||||||
|
</findStrings>
|
||||||
|
</component>
|
||||||
|
<component name="IdeDocumentHistory">
|
||||||
|
<option name="CHANGED_PATHS">
|
||||||
|
<list>
|
||||||
|
<option value="$PROJECT_DIR$/../LoPy_testproject/nanogateway.py" />
|
||||||
|
<option value="$PROJECT_DIR$/../LoPy_testproject/main.py" />
|
||||||
|
<option value="$PROJECT_DIR$/../LoPy_testproject/config.py" />
|
||||||
|
<option value="$PROJECT_DIR$/config.py" />
|
||||||
|
<option value="$PROJECT_DIR$/dth.py" />
|
||||||
|
<option value="$PROJECT_DIR$/main.py" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="MicroPythonDevices" devicePath="/dev/ttyUSB0" />
|
||||||
|
<component name="ProjectFrameBounds" extendedState="6">
|
||||||
|
<option name="x" value="371" />
|
||||||
|
<option name="width" value="1050" />
|
||||||
|
<option name="height" value="568" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectView">
|
||||||
|
<navigator proportions="" version="1">
|
||||||
|
<foldersAlwaysOnTop value="true" />
|
||||||
|
</navigator>
|
||||||
|
<panes>
|
||||||
|
<pane id="ProjectPane">
|
||||||
|
<subPane>
|
||||||
|
<expand>
|
||||||
|
<path>
|
||||||
|
<item name="humidity_sensor_am2305" type="b2602c69:ProjectViewProjectNode" />
|
||||||
|
<item name="humidity_sensor_am2305" type="462c0819:PsiDirectoryNode" />
|
||||||
|
</path>
|
||||||
|
</expand>
|
||||||
|
<select />
|
||||||
|
</subPane>
|
||||||
|
</pane>
|
||||||
|
<pane id="Scope" />
|
||||||
|
</panes>
|
||||||
|
</component>
|
||||||
|
<component name="PropertiesComponent">
|
||||||
|
<property name="last_opened_file_path" value="$PROJECT_DIR$" />
|
||||||
|
<property name="settings.editor.selected.configurable" value="com.jetbrains.python.configuration.PythonContentEntriesConfigurable" />
|
||||||
|
</component>
|
||||||
|
<component name="RunDashboard">
|
||||||
|
<option name="ruleStates">
|
||||||
|
<list>
|
||||||
|
<RuleState>
|
||||||
|
<option name="name" value="ConfigurationTypeDashboardGroupingRule" />
|
||||||
|
</RuleState>
|
||||||
|
<RuleState>
|
||||||
|
<option name="name" value="StatusDashboardGroupingRule" />
|
||||||
|
</RuleState>
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="RunManager">
|
||||||
|
<configuration name="Flash" type="MicroPythonConfigurationType" factoryName="MicroPython" path="$PROJECT_DIR$">
|
||||||
|
<module name="lopy_beacon" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
|
<component name="SvnConfiguration">
|
||||||
|
<configuration />
|
||||||
|
</component>
|
||||||
|
<component name="TaskManager">
|
||||||
|
<task active="true" id="Default" summary="Default task">
|
||||||
|
<changelist id="4e6029b8-a0fe-4700-9dd8-e219ffe8ed9c" name="Default" comment="" />
|
||||||
|
<created>1525978521493</created>
|
||||||
|
<option name="number" value="Default" />
|
||||||
|
<option name="presentableId" value="Default" />
|
||||||
|
<updated>1525978521493</updated>
|
||||||
|
</task>
|
||||||
|
<servers />
|
||||||
|
</component>
|
||||||
|
<component name="TodoView">
|
||||||
|
<todo-panel id="selected-file">
|
||||||
|
<is-autoscroll-to-source value="true" />
|
||||||
|
</todo-panel>
|
||||||
|
<todo-panel id="all">
|
||||||
|
<are-packages-shown value="true" />
|
||||||
|
<is-autoscroll-to-source value="true" />
|
||||||
|
</todo-panel>
|
||||||
|
</component>
|
||||||
|
<component name="ToolWindowManager">
|
||||||
|
<frame x="-4" y="0" width="1608" height="868" extended-state="6" />
|
||||||
|
<editor active="true" />
|
||||||
|
<layout>
|
||||||
|
<window_info active="true" content_ui="combo" id="Project" order="0" visible="true" weight="0.22384274" />
|
||||||
|
<window_info id="Structure" order="1" side_tool="true" weight="0.25" />
|
||||||
|
<window_info id="Favorites" order="2" side_tool="true" />
|
||||||
|
<window_info anchor="bottom" id="Message" order="0" />
|
||||||
|
<window_info anchor="bottom" id="Find" order="1" />
|
||||||
|
<window_info anchor="bottom" id="Run" order="2" sideWeight="0.49968293" visible="true" weight="0.33604336" />
|
||||||
|
<window_info anchor="bottom" id="Debug" order="3" weight="0.4" />
|
||||||
|
<window_info anchor="bottom" id="Cvs" order="4" weight="0.25" />
|
||||||
|
<window_info anchor="bottom" id="Inspection" order="5" weight="0.4" />
|
||||||
|
<window_info anchor="bottom" id="TODO" order="6" weight="0.3292683" />
|
||||||
|
<window_info anchor="bottom" id="Terminal" order="7" sideWeight="0.49904883" weight="0.32249323" />
|
||||||
|
<window_info anchor="bottom" id="Python Console" order="8" weight="0.32791328" />
|
||||||
|
<window_info anchor="bottom" id="Version Control" order="9" show_stripe_button="false" />
|
||||||
|
<window_info anchor="bottom" id="Event Log" order="10" sideWeight="0.5009512" side_tool="true" weight="0.39701897" />
|
||||||
|
<window_info anchor="right" id="Commander" internal_type="SLIDING" order="0" type="SLIDING" weight="0.4" />
|
||||||
|
<window_info anchor="right" id="Ant Build" order="1" weight="0.25" />
|
||||||
|
<window_info anchor="right" content_ui="combo" id="Hierarchy" order="2" weight="0.25" />
|
||||||
|
</layout>
|
||||||
|
</component>
|
||||||
|
<component name="editorHistoryManager">
|
||||||
|
<entry file="file://$PROJECT_DIR$/../LoPy_testproject/nanogateway.py">
|
||||||
|
<provider selected="true" editor-type-id="text-editor">
|
||||||
|
<state relative-caret-position="6">
|
||||||
|
<caret line="357" lean-forward="true" selection-start-line="357" selection-end-line="357" />
|
||||||
|
</state>
|
||||||
|
</provider>
|
||||||
|
</entry>
|
||||||
|
<entry file="file://$PROJECT_DIR$/../LoPy_testproject/main.py">
|
||||||
|
<provider selected="true" editor-type-id="text-editor">
|
||||||
|
<state relative-caret-position="340">
|
||||||
|
<caret line="20" column="7" selection-start-line="20" selection-start-column="7" selection-end-line="20" selection-end-column="7" />
|
||||||
|
<folding>
|
||||||
|
<element signature="e#49#62#0" expanded="true" />
|
||||||
|
</folding>
|
||||||
|
</state>
|
||||||
|
</provider>
|
||||||
|
</entry>
|
||||||
|
<entry file="file://$PROJECT_DIR$/../LoPy_testproject/config.py">
|
||||||
|
<provider selected="true" editor-type-id="text-editor">
|
||||||
|
<state relative-caret-position="255">
|
||||||
|
<caret line="16" column="14" selection-start-line="16" selection-start-column="14" selection-end-line="16" selection-end-column="14" />
|
||||||
|
</state>
|
||||||
|
</provider>
|
||||||
|
</entry>
|
||||||
|
<entry file="file://$USER_HOME$/.local/share/applications/jetbrains-pycharm-ce.desktop">
|
||||||
|
<provider selected="true" editor-type-id="text-editor" />
|
||||||
|
</entry>
|
||||||
|
<entry file="file://$PROJECT_DIR$/dth.py">
|
||||||
|
<provider selected="true" editor-type-id="text-editor">
|
||||||
|
<state relative-caret-position="629">
|
||||||
|
<caret line="40" column="48" lean-forward="true" selection-start-line="40" selection-start-column="48" selection-end-line="40" selection-end-column="48" />
|
||||||
|
<folding>
|
||||||
|
<element signature="e#0#11#0" expanded="true" />
|
||||||
|
</folding>
|
||||||
|
</state>
|
||||||
|
</provider>
|
||||||
|
</entry>
|
||||||
|
<entry file="file://$PROJECT_DIR$/config.py">
|
||||||
|
<provider selected="true" editor-type-id="text-editor">
|
||||||
|
<state relative-caret-position="68">
|
||||||
|
<caret line="4" selection-start-line="4" selection-end-line="4" />
|
||||||
|
<folding>
|
||||||
|
<element signature="e#57#71#0" expanded="true" />
|
||||||
|
</folding>
|
||||||
|
</state>
|
||||||
|
</provider>
|
||||||
|
</entry>
|
||||||
|
<entry file="file://$PROJECT_DIR$/main.py">
|
||||||
|
<provider selected="true" editor-type-id="text-editor">
|
||||||
|
<state relative-caret-position="336">
|
||||||
|
<caret line="123" column="13" lean-forward="true" selection-start-line="123" selection-start-column="13" selection-end-line="123" selection-end-column="13" />
|
||||||
|
<folding>
|
||||||
|
<element signature="e#86#110#0" expanded="true" />
|
||||||
|
</folding>
|
||||||
|
</state>
|
||||||
|
</provider>
|
||||||
|
</entry>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
14
config.py
Normal file
14
config.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
""" LoPy LoRaWAN Nano Gateway configuration options """
|
||||||
|
|
||||||
|
import machine
|
||||||
|
import ubinascii
|
||||||
|
|
||||||
|
# for EU868
|
||||||
|
LORA_FREQUENCY = 868100000
|
||||||
|
LORA_GW_DR = "SF7BW125" # DR_5
|
||||||
|
LORA_NODE_DR = 5
|
||||||
|
|
||||||
|
# for US915
|
||||||
|
# LORA_FREQUENCY = 903900000
|
||||||
|
# LORA_GW_DR = "SF7BW125" # DR_3
|
||||||
|
# LORA_NODE_DR = 3
|
||||||
94
dth.py
Normal file
94
dth.py
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
import time
|
||||||
|
import pycom
|
||||||
|
from machine import enable_irq, disable_irq, Pin
|
||||||
|
|
||||||
|
|
||||||
|
class DTHResult:
|
||||||
|
'DHT sensor result returned by DHT.read() method'
|
||||||
|
|
||||||
|
ERR_NO_ERROR = 0
|
||||||
|
ERR_MISSING_DATA = 1
|
||||||
|
ERR_CRC = 2
|
||||||
|
|
||||||
|
error_code = ERR_NO_ERROR
|
||||||
|
temperature = -1
|
||||||
|
humidity = -1
|
||||||
|
|
||||||
|
def __init__(self, error_code, temperature, humidity):
|
||||||
|
self.error_code = error_code
|
||||||
|
self.temperature = temperature
|
||||||
|
self.humidity = humidity
|
||||||
|
|
||||||
|
def is_valid(self):
|
||||||
|
return self.error_code == DTHResult.ERR_NO_ERROR
|
||||||
|
|
||||||
|
|
||||||
|
class DTH:
|
||||||
|
'DHT sensor (dht11, dht21,dht22) reader class for Pycom'
|
||||||
|
|
||||||
|
# __pin = Pin('P3', mode=Pin.OPEN_DRAIN)
|
||||||
|
__dhttype = 0
|
||||||
|
|
||||||
|
def __init__(self, pin, sensor=0):
|
||||||
|
self.__pin = Pin(pin, mode=Pin.OPEN_DRAIN)
|
||||||
|
self.__dhttype = sensor
|
||||||
|
self.__pin(1)
|
||||||
|
time.sleep(1.0)
|
||||||
|
|
||||||
|
def read(self):
|
||||||
|
# pull down to low
|
||||||
|
self.__send_and_sleep(0, 0.019)
|
||||||
|
data = pycom.pulses_get(self.__pin, 100)
|
||||||
|
self.__pin.init(Pin.OPEN_DRAIN)
|
||||||
|
self.__pin(1)
|
||||||
|
# print(data)
|
||||||
|
bits = []
|
||||||
|
for a, b in data:
|
||||||
|
if a == 1 and 18 <= b <= 28:
|
||||||
|
bits.append(0)
|
||||||
|
if a == 1 and 65 <= b <= 75:
|
||||||
|
bits.append(1)
|
||||||
|
# print("longueur bits : %d " % len(bits))
|
||||||
|
if len(bits) != 40:
|
||||||
|
return DTHResult(DTHResult.ERR_MISSING_DATA, 0, 0)
|
||||||
|
# print(bits)
|
||||||
|
# we have the bits, calculate bytes
|
||||||
|
the_bytes = self.__bits_to_bytes(bits)
|
||||||
|
# calculate checksum and check
|
||||||
|
checksum = self.__calculate_checksum(the_bytes)
|
||||||
|
if the_bytes[4] != checksum:
|
||||||
|
return DTHResult(DTHResult.ERR_CRC, 0, 0)
|
||||||
|
# ok, we have valid data, return it
|
||||||
|
[int_rh, dec_rh, int_t, dec_t, csum] = the_bytes
|
||||||
|
if self.__dhttype == 0: # dht11
|
||||||
|
rh = int_rh # dht11 20% ~ 90%
|
||||||
|
t = int_t # dht11 0..50°C
|
||||||
|
else: # dht21,dht22
|
||||||
|
rh = ((int_rh * 256) + dec_rh) / 10
|
||||||
|
t = (((int_t & 0x7F) * 256) + dec_t) / 10
|
||||||
|
if (int_t & 0x80) > 0:
|
||||||
|
t *= -1
|
||||||
|
return DTHResult(DTHResult.ERR_NO_ERROR, t, rh)
|
||||||
|
|
||||||
|
def __send_and_sleep(self, output, mysleep):
|
||||||
|
self.__pin(output)
|
||||||
|
time.sleep(mysleep)
|
||||||
|
|
||||||
|
def __bits_to_bytes(self, bits):
|
||||||
|
the_bytes = []
|
||||||
|
byte = 0
|
||||||
|
|
||||||
|
for i in range(0, len(bits)):
|
||||||
|
byte = byte << 1
|
||||||
|
if (bits[i]):
|
||||||
|
byte = byte | 1
|
||||||
|
else:
|
||||||
|
byte = byte | 0
|
||||||
|
if ((i + 1) % 8 == 0):
|
||||||
|
the_bytes.append(byte)
|
||||||
|
byte = 0
|
||||||
|
# print(the_bytes)
|
||||||
|
return the_bytes
|
||||||
|
|
||||||
|
def __calculate_checksum(self, the_bytes):
|
||||||
|
return the_bytes[0] + the_bytes[1] + the_bytes[2] + the_bytes[3] & 255
|
||||||
126
main.py
Normal file
126
main.py
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
""" humidity and temperature sensor AM2305 """
|
||||||
|
""" reads humidity and temperature and sends it via LoRaWAN"""
|
||||||
|
|
||||||
|
from network import LoRa
|
||||||
|
import socket
|
||||||
|
import ubinascii
|
||||||
|
import struct
|
||||||
|
import time
|
||||||
|
import config
|
||||||
|
from machine import Pin
|
||||||
|
from machine import WDT
|
||||||
|
import machine
|
||||||
|
import pycom
|
||||||
|
from dth import DTH
|
||||||
|
|
||||||
|
SENSOR_PIN = 'P23'
|
||||||
|
FLASH_PIN = 'P2'
|
||||||
|
|
||||||
|
def generate_lora_message():
|
||||||
|
|
||||||
|
th = DTH('P23', 1)
|
||||||
|
result = th.read()
|
||||||
|
temp = '{:3.2f}'.format(result.temperature / 1.0)
|
||||||
|
humi = '{:3.2f}'.format(result.humidity / 1.0)
|
||||||
|
|
||||||
|
__message__ = '/Temp:' + str(temp) + "C/Humi:" + str(humi) + '%'
|
||||||
|
|
||||||
|
print("Generated message: " + str(__message__))
|
||||||
|
|
||||||
|
return __message__
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
pycom.heartbeat(False)
|
||||||
|
wdt = WDT(timeout=40000) # enable it with a timeout of 2 seconds
|
||||||
|
wdt.feed()
|
||||||
|
print("Waking up...")
|
||||||
|
|
||||||
|
# initialize LoRa in LORAWAN mode.
|
||||||
|
# Please pick the region that matches where you are using the device:
|
||||||
|
# Asia = LoRa.AS923
|
||||||
|
# Australia = LoRa.AU915
|
||||||
|
# Europe = LoRa.EU868
|
||||||
|
# United States = LoRa.US915
|
||||||
|
lora = LoRa(mode=LoRa.LORAWAN, region=LoRa.EU868)
|
||||||
|
lora.init(mode=LoRa.LORAWAN)
|
||||||
|
|
||||||
|
lora.nvram_restore()
|
||||||
|
|
||||||
|
# create an OTA authentication params
|
||||||
|
dev_eui = ubinascii.unhexlify('70B3D5499DB25D35') # these settings can be found from TTN
|
||||||
|
app_eui = ubinascii.unhexlify('70B3D57ED000D35F') # these settings can be found from TTN
|
||||||
|
app_key = ubinascii.unhexlify('A36F43FF887069841A6C421DC20B574B') # these settings can be found from TTN
|
||||||
|
|
||||||
|
# set the 3 default channels to the same frequency (must be before sending the OTAA join request)
|
||||||
|
lora.add_channel(0, frequency=config.LORA_FREQUENCY, dr_min=0, dr_max=5)
|
||||||
|
lora.add_channel(1, frequency=config.LORA_FREQUENCY, dr_min=0, dr_max=5)
|
||||||
|
lora.add_channel(2, frequency=config.LORA_FREQUENCY, dr_min=0, dr_max=5)
|
||||||
|
|
||||||
|
if not lora.has_joined():
|
||||||
|
# join a network using OTAA
|
||||||
|
print("Joining LoRa network...")
|
||||||
|
lora.join(activation=LoRa.OTAA, auth=(dev_eui, app_eui, app_key), timeout=0, dr=config.LORA_NODE_DR)
|
||||||
|
|
||||||
|
# wait until the module has joined the network
|
||||||
|
retry_cnt = 0
|
||||||
|
sleep_cnt = 0
|
||||||
|
while not lora.has_joined():
|
||||||
|
time.sleep(2.5)
|
||||||
|
print('Not joined yet...')
|
||||||
|
retry_cnt = retry_cnt + 1
|
||||||
|
|
||||||
|
if retry_cnt > 10:
|
||||||
|
lora.join(activation=LoRa.OTAA, auth=(dev_eui, app_eui, app_key), timeout=0, dr=config.LORA_NODE_DR)
|
||||||
|
print('Sending join request again...')
|
||||||
|
retry_cnt = 0
|
||||||
|
sleep_cnt = sleep_cnt + 1
|
||||||
|
|
||||||
|
if sleep_cnt > 10:
|
||||||
|
sleep_cnt = 0
|
||||||
|
print('Could not connect! Entering deep sleep...')
|
||||||
|
machine.deepsleep(200000)
|
||||||
|
|
||||||
|
# remove all the non-default channels
|
||||||
|
for i in range(3, 16):
|
||||||
|
lora.remove_channel(i)
|
||||||
|
else:
|
||||||
|
print("Woke from deepsleep... already joined...")
|
||||||
|
|
||||||
|
# create a LoRa socket
|
||||||
|
s = socket.socket(socket.AF_LORA, socket.SOCK_RAW)
|
||||||
|
|
||||||
|
# set the LoRaWAN data rate
|
||||||
|
s.setsockopt(socket.SOL_LORA, socket.SO_DR, config.LORA_NODE_DR)
|
||||||
|
|
||||||
|
# make the socket blocking
|
||||||
|
s.setblocking(False)
|
||||||
|
|
||||||
|
# generate message
|
||||||
|
|
||||||
|
lora.nvram_save()
|
||||||
|
|
||||||
|
flashing = Pin(FLASH_PIN, mode=Pin.IN, pull=Pin.PULL_UP)
|
||||||
|
sleep = 1
|
||||||
|
wdt.feed()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
|
||||||
|
if not flashing():
|
||||||
|
print("User Button was pressed! No deep sleep anymore...")
|
||||||
|
sleep = 0
|
||||||
|
|
||||||
|
# if a message was received we go back to sleep
|
||||||
|
rx, port = s.recvfrom(256)
|
||||||
|
if rx:
|
||||||
|
print('Received: {}, on port: {}'.format(rx, port))
|
||||||
|
print('Sleeping is enabled again...')
|
||||||
|
sleep = 1
|
||||||
|
|
||||||
|
msg = generate_lora_message()
|
||||||
|
|
||||||
|
s.send(msg)
|
||||||
|
time.sleep(10)
|
||||||
|
if sleep:
|
||||||
|
print("Entering deep sleep....")
|
||||||
|
machine.deepsleep(500000)
|
||||||
76
venv/bin/activate
Normal file
76
venv/bin/activate
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
# This file must be used with "source bin/activate" *from bash*
|
||||||
|
# you cannot run it directly
|
||||||
|
|
||||||
|
deactivate () {
|
||||||
|
# reset old environment variables
|
||||||
|
if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
|
||||||
|
PATH="${_OLD_VIRTUAL_PATH:-}"
|
||||||
|
export PATH
|
||||||
|
unset _OLD_VIRTUAL_PATH
|
||||||
|
fi
|
||||||
|
if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
|
||||||
|
PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
|
||||||
|
export PYTHONHOME
|
||||||
|
unset _OLD_VIRTUAL_PYTHONHOME
|
||||||
|
fi
|
||||||
|
|
||||||
|
# This should detect bash and zsh, which have a hash command that must
|
||||||
|
# be called to get it to forget past commands. Without forgetting
|
||||||
|
# past commands the $PATH changes we made may not be respected
|
||||||
|
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
|
||||||
|
hash -r
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
|
||||||
|
PS1="${_OLD_VIRTUAL_PS1:-}"
|
||||||
|
export PS1
|
||||||
|
unset _OLD_VIRTUAL_PS1
|
||||||
|
fi
|
||||||
|
|
||||||
|
unset VIRTUAL_ENV
|
||||||
|
if [ ! "$1" = "nondestructive" ] ; then
|
||||||
|
# Self destruct!
|
||||||
|
unset -f deactivate
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# unset irrelevant variables
|
||||||
|
deactivate nondestructive
|
||||||
|
|
||||||
|
VIRTUAL_ENV="/home/stephan/PycharmProjects/lopy_beacon/venv"
|
||||||
|
export VIRTUAL_ENV
|
||||||
|
|
||||||
|
_OLD_VIRTUAL_PATH="$PATH"
|
||||||
|
PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||||
|
export PATH
|
||||||
|
|
||||||
|
# unset PYTHONHOME if set
|
||||||
|
# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
|
||||||
|
# could use `if (set -u; : $PYTHONHOME) ;` in bash
|
||||||
|
if [ -n "${PYTHONHOME:-}" ] ; then
|
||||||
|
_OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
|
||||||
|
unset PYTHONHOME
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
|
||||||
|
_OLD_VIRTUAL_PS1="${PS1:-}"
|
||||||
|
if [ "x(venv) " != x ] ; then
|
||||||
|
PS1="(venv) ${PS1:-}"
|
||||||
|
else
|
||||||
|
if [ "`basename \"$VIRTUAL_ENV\"`" = "__" ] ; then
|
||||||
|
# special case for Aspen magic directories
|
||||||
|
# see http://www.zetadev.com/software/aspen/
|
||||||
|
PS1="[`basename \`dirname \"$VIRTUAL_ENV\"\``] $PS1"
|
||||||
|
else
|
||||||
|
PS1="(`basename \"$VIRTUAL_ENV\"`)$PS1"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
export PS1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# This should detect bash and zsh, which have a hash command that must
|
||||||
|
# be called to get it to forget past commands. Without forgetting
|
||||||
|
# past commands the $PATH changes we made may not be respected
|
||||||
|
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
|
||||||
|
hash -r
|
||||||
|
fi
|
||||||
37
venv/bin/activate.csh
Normal file
37
venv/bin/activate.csh
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# This file must be used with "source bin/activate.csh" *from csh*.
|
||||||
|
# You cannot run it directly.
|
||||||
|
# Created by Davide Di Blasi <davidedb@gmail.com>.
|
||||||
|
# Ported to Python 3.3 venv by Andrew Svetlov <andrew.svetlov@gmail.com>
|
||||||
|
|
||||||
|
alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; test "\!:*" != "nondestructive" && unalias deactivate'
|
||||||
|
|
||||||
|
# Unset irrelevant variables.
|
||||||
|
deactivate nondestructive
|
||||||
|
|
||||||
|
setenv VIRTUAL_ENV "/home/stephan/PycharmProjects/lopy_beacon/venv"
|
||||||
|
|
||||||
|
set _OLD_VIRTUAL_PATH="$PATH"
|
||||||
|
setenv PATH "$VIRTUAL_ENV/bin:$PATH"
|
||||||
|
|
||||||
|
|
||||||
|
set _OLD_VIRTUAL_PROMPT="$prompt"
|
||||||
|
|
||||||
|
if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
|
||||||
|
if ("venv" != "") then
|
||||||
|
set env_name = "venv"
|
||||||
|
else
|
||||||
|
if (`basename "VIRTUAL_ENV"` == "__") then
|
||||||
|
# special case for Aspen magic directories
|
||||||
|
# see http://www.zetadev.com/software/aspen/
|
||||||
|
set env_name = `basename \`dirname "$VIRTUAL_ENV"\``
|
||||||
|
else
|
||||||
|
set env_name = `basename "$VIRTUAL_ENV"`
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
set prompt = "[$env_name] $prompt"
|
||||||
|
unset env_name
|
||||||
|
endif
|
||||||
|
|
||||||
|
alias pydoc python -m pydoc
|
||||||
|
|
||||||
|
rehash
|
||||||
75
venv/bin/activate.fish
Normal file
75
venv/bin/activate.fish
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
# This file must be used with ". bin/activate.fish" *from fish* (http://fishshell.org)
|
||||||
|
# you cannot run it directly
|
||||||
|
|
||||||
|
function deactivate -d "Exit virtualenv and return to normal shell environment"
|
||||||
|
# reset old environment variables
|
||||||
|
if test -n "$_OLD_VIRTUAL_PATH"
|
||||||
|
set -gx PATH $_OLD_VIRTUAL_PATH
|
||||||
|
set -e _OLD_VIRTUAL_PATH
|
||||||
|
end
|
||||||
|
if test -n "$_OLD_VIRTUAL_PYTHONHOME"
|
||||||
|
set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME
|
||||||
|
set -e _OLD_VIRTUAL_PYTHONHOME
|
||||||
|
end
|
||||||
|
|
||||||
|
if test -n "$_OLD_FISH_PROMPT_OVERRIDE"
|
||||||
|
functions -e fish_prompt
|
||||||
|
set -e _OLD_FISH_PROMPT_OVERRIDE
|
||||||
|
functions -c _old_fish_prompt fish_prompt
|
||||||
|
functions -e _old_fish_prompt
|
||||||
|
end
|
||||||
|
|
||||||
|
set -e VIRTUAL_ENV
|
||||||
|
if test "$argv[1]" != "nondestructive"
|
||||||
|
# Self destruct!
|
||||||
|
functions -e deactivate
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# unset irrelevant variables
|
||||||
|
deactivate nondestructive
|
||||||
|
|
||||||
|
set -gx VIRTUAL_ENV "/home/stephan/PycharmProjects/lopy_beacon/venv"
|
||||||
|
|
||||||
|
set -gx _OLD_VIRTUAL_PATH $PATH
|
||||||
|
set -gx PATH "$VIRTUAL_ENV/bin" $PATH
|
||||||
|
|
||||||
|
# unset PYTHONHOME if set
|
||||||
|
if set -q PYTHONHOME
|
||||||
|
set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME
|
||||||
|
set -e PYTHONHOME
|
||||||
|
end
|
||||||
|
|
||||||
|
if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
|
||||||
|
# fish uses a function instead of an env var to generate the prompt.
|
||||||
|
|
||||||
|
# save the current fish_prompt function as the function _old_fish_prompt
|
||||||
|
functions -c fish_prompt _old_fish_prompt
|
||||||
|
|
||||||
|
# with the original prompt function renamed, we can override with our own.
|
||||||
|
function fish_prompt
|
||||||
|
# Save the return status of the last command
|
||||||
|
set -l old_status $status
|
||||||
|
|
||||||
|
# Prompt override?
|
||||||
|
if test -n "(venv) "
|
||||||
|
printf "%s%s" "(venv) " (set_color normal)
|
||||||
|
else
|
||||||
|
# ...Otherwise, prepend env
|
||||||
|
set -l _checkbase (basename "$VIRTUAL_ENV")
|
||||||
|
if test $_checkbase = "__"
|
||||||
|
# special case for Aspen magic directories
|
||||||
|
# see http://www.zetadev.com/software/aspen/
|
||||||
|
printf "%s[%s]%s " (set_color -b blue white) (basename (dirname "$VIRTUAL_ENV")) (set_color normal)
|
||||||
|
else
|
||||||
|
printf "%s(%s)%s" (set_color -b blue white) (basename "$VIRTUAL_ENV") (set_color normal)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Restore the return status of the previous command.
|
||||||
|
echo "exit $old_status" | .
|
||||||
|
_old_fish_prompt
|
||||||
|
end
|
||||||
|
|
||||||
|
set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
|
||||||
|
end
|
||||||
11
venv/bin/ampy
Executable file
11
venv/bin/ampy
Executable file
@ -0,0 +1,11 @@
|
|||||||
|
#!/home/stephan/PycharmProjects/lopy_beacon/venv/bin/python
|
||||||
|
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from ampy.cli import cli
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(cli())
|
||||||
11
venv/bin/dotenv
Executable file
11
venv/bin/dotenv
Executable file
@ -0,0 +1,11 @@
|
|||||||
|
#!/home/stephan/PycharmProjects/lopy_beacon/venv/bin/python
|
||||||
|
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from dotenv.cli import cli
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(cli())
|
||||||
12
venv/bin/easy_install
Executable file
12
venv/bin/easy_install
Executable file
@ -0,0 +1,12 @@
|
|||||||
|
#!/home/stephan/PycharmProjects/lopy_beacon/venv/bin/python
|
||||||
|
# EASY-INSTALL-ENTRY-SCRIPT: 'setuptools==39.1.0','console_scripts','easy_install'
|
||||||
|
__requires__ = 'setuptools==39.1.0'
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pkg_resources import load_entry_point
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(
|
||||||
|
load_entry_point('setuptools==39.1.0', 'console_scripts', 'easy_install')()
|
||||||
|
)
|
||||||
12
venv/bin/easy_install-3.6
Executable file
12
venv/bin/easy_install-3.6
Executable file
@ -0,0 +1,12 @@
|
|||||||
|
#!/home/stephan/PycharmProjects/lopy_beacon/venv/bin/python
|
||||||
|
# EASY-INSTALL-ENTRY-SCRIPT: 'setuptools==39.1.0','console_scripts','easy_install-3.6'
|
||||||
|
__requires__ = 'setuptools==39.1.0'
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pkg_resources import load_entry_point
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(
|
||||||
|
load_entry_point('setuptools==39.1.0', 'console_scripts', 'easy_install-3.6')()
|
||||||
|
)
|
||||||
976
venv/bin/miniterm.py
Executable file
976
venv/bin/miniterm.py
Executable file
@ -0,0 +1,976 @@
|
|||||||
|
#!/home/stephan/PycharmProjects/lopy_beacon/venv/bin/python
|
||||||
|
#
|
||||||
|
# Very simple serial terminal
|
||||||
|
#
|
||||||
|
# This file is part of pySerial. https://github.com/pyserial/pyserial
|
||||||
|
# (C)2002-2015 Chris Liechti <cliechti@gmx.net>
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
import codecs
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import threading
|
||||||
|
|
||||||
|
import serial
|
||||||
|
from serial.tools.list_ports import comports
|
||||||
|
from serial.tools import hexlify_codec
|
||||||
|
|
||||||
|
# pylint: disable=wrong-import-order,wrong-import-position
|
||||||
|
|
||||||
|
codecs.register(lambda c: hexlify_codec.getregentry() if c == 'hexlify' else None)
|
||||||
|
|
||||||
|
try:
|
||||||
|
raw_input
|
||||||
|
except NameError:
|
||||||
|
# pylint: disable=redefined-builtin,invalid-name
|
||||||
|
raw_input = input # in python3 it's "raw"
|
||||||
|
unichr = chr
|
||||||
|
|
||||||
|
|
||||||
|
def key_description(character):
|
||||||
|
"""generate a readable description for a key"""
|
||||||
|
ascii_code = ord(character)
|
||||||
|
if ascii_code < 32:
|
||||||
|
return 'Ctrl+{:c}'.format(ord('@') + ascii_code)
|
||||||
|
else:
|
||||||
|
return repr(character)
|
||||||
|
|
||||||
|
|
||||||
|
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
class ConsoleBase(object):
|
||||||
|
"""OS abstraction for console (input/output codec, no echo)"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
if sys.version_info >= (3, 0):
|
||||||
|
self.byte_output = sys.stdout.buffer
|
||||||
|
else:
|
||||||
|
self.byte_output = sys.stdout
|
||||||
|
self.output = sys.stdout
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
"""Set console to read single characters, no echo"""
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
"""Restore default console settings"""
|
||||||
|
|
||||||
|
def getkey(self):
|
||||||
|
"""Read a single key from the console"""
|
||||||
|
return None
|
||||||
|
|
||||||
|
def write_bytes(self, byte_string):
|
||||||
|
"""Write bytes (already encoded)"""
|
||||||
|
self.byte_output.write(byte_string)
|
||||||
|
self.byte_output.flush()
|
||||||
|
|
||||||
|
def write(self, text):
|
||||||
|
"""Write string"""
|
||||||
|
self.output.write(text)
|
||||||
|
self.output.flush()
|
||||||
|
|
||||||
|
def cancel(self):
|
||||||
|
"""Cancel getkey operation"""
|
||||||
|
|
||||||
|
# - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
# context manager:
|
||||||
|
# switch terminal temporary to normal mode (e.g. to get user input)
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
self.cleanup()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, *args, **kwargs):
|
||||||
|
self.setup()
|
||||||
|
|
||||||
|
|
||||||
|
if os.name == 'nt': # noqa
|
||||||
|
import msvcrt
|
||||||
|
import ctypes
|
||||||
|
|
||||||
|
class Out(object):
|
||||||
|
"""file-like wrapper that uses os.write"""
|
||||||
|
|
||||||
|
def __init__(self, fd):
|
||||||
|
self.fd = fd
|
||||||
|
|
||||||
|
def flush(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def write(self, s):
|
||||||
|
os.write(self.fd, s)
|
||||||
|
|
||||||
|
class Console(ConsoleBase):
|
||||||
|
def __init__(self):
|
||||||
|
super(Console, self).__init__()
|
||||||
|
self._saved_ocp = ctypes.windll.kernel32.GetConsoleOutputCP()
|
||||||
|
self._saved_icp = ctypes.windll.kernel32.GetConsoleCP()
|
||||||
|
ctypes.windll.kernel32.SetConsoleOutputCP(65001)
|
||||||
|
ctypes.windll.kernel32.SetConsoleCP(65001)
|
||||||
|
self.output = codecs.getwriter('UTF-8')(Out(sys.stdout.fileno()), 'replace')
|
||||||
|
# the change of the code page is not propagated to Python, manually fix it
|
||||||
|
sys.stderr = codecs.getwriter('UTF-8')(Out(sys.stderr.fileno()), 'replace')
|
||||||
|
sys.stdout = self.output
|
||||||
|
self.output.encoding = 'UTF-8' # needed for input
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
ctypes.windll.kernel32.SetConsoleOutputCP(self._saved_ocp)
|
||||||
|
ctypes.windll.kernel32.SetConsoleCP(self._saved_icp)
|
||||||
|
|
||||||
|
def getkey(self):
|
||||||
|
while True:
|
||||||
|
z = msvcrt.getwch()
|
||||||
|
if z == unichr(13):
|
||||||
|
return unichr(10)
|
||||||
|
elif z in (unichr(0), unichr(0x0e)): # functions keys, ignore
|
||||||
|
msvcrt.getwch()
|
||||||
|
else:
|
||||||
|
return z
|
||||||
|
|
||||||
|
def cancel(self):
|
||||||
|
# CancelIo, CancelSynchronousIo do not seem to work when using
|
||||||
|
# getwch, so instead, send a key to the window with the console
|
||||||
|
hwnd = ctypes.windll.kernel32.GetConsoleWindow()
|
||||||
|
ctypes.windll.user32.PostMessageA(hwnd, 0x100, 0x0d, 0)
|
||||||
|
|
||||||
|
elif os.name == 'posix':
|
||||||
|
import atexit
|
||||||
|
import termios
|
||||||
|
import fcntl
|
||||||
|
|
||||||
|
class Console(ConsoleBase):
|
||||||
|
def __init__(self):
|
||||||
|
super(Console, self).__init__()
|
||||||
|
self.fd = sys.stdin.fileno()
|
||||||
|
self.old = termios.tcgetattr(self.fd)
|
||||||
|
atexit.register(self.cleanup)
|
||||||
|
if sys.version_info < (3, 0):
|
||||||
|
self.enc_stdin = codecs.getreader(sys.stdin.encoding)(sys.stdin)
|
||||||
|
else:
|
||||||
|
self.enc_stdin = sys.stdin
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
new = termios.tcgetattr(self.fd)
|
||||||
|
new[3] = new[3] & ~termios.ICANON & ~termios.ECHO & ~termios.ISIG
|
||||||
|
new[6][termios.VMIN] = 1
|
||||||
|
new[6][termios.VTIME] = 0
|
||||||
|
termios.tcsetattr(self.fd, termios.TCSANOW, new)
|
||||||
|
|
||||||
|
def getkey(self):
|
||||||
|
c = self.enc_stdin.read(1)
|
||||||
|
if c == unichr(0x7f):
|
||||||
|
c = unichr(8) # map the BS key (which yields DEL) to backspace
|
||||||
|
return c
|
||||||
|
|
||||||
|
def cancel(self):
|
||||||
|
fcntl.ioctl(self.fd, termios.TIOCSTI, b'\0')
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old)
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise NotImplementedError(
|
||||||
|
'Sorry no implementation for your platform ({}) available.'.format(sys.platform))
|
||||||
|
|
||||||
|
|
||||||
|
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
class Transform(object):
|
||||||
|
"""do-nothing: forward all data unchanged"""
|
||||||
|
def rx(self, text):
|
||||||
|
"""text received from serial port"""
|
||||||
|
return text
|
||||||
|
|
||||||
|
def tx(self, text):
|
||||||
|
"""text to be sent to serial port"""
|
||||||
|
return text
|
||||||
|
|
||||||
|
def echo(self, text):
|
||||||
|
"""text to be sent but displayed on console"""
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
class CRLF(Transform):
|
||||||
|
"""ENTER sends CR+LF"""
|
||||||
|
|
||||||
|
def tx(self, text):
|
||||||
|
return text.replace('\n', '\r\n')
|
||||||
|
|
||||||
|
|
||||||
|
class CR(Transform):
|
||||||
|
"""ENTER sends CR"""
|
||||||
|
|
||||||
|
def rx(self, text):
|
||||||
|
return text.replace('\r', '\n')
|
||||||
|
|
||||||
|
def tx(self, text):
|
||||||
|
return text.replace('\n', '\r')
|
||||||
|
|
||||||
|
|
||||||
|
class LF(Transform):
|
||||||
|
"""ENTER sends LF"""
|
||||||
|
|
||||||
|
|
||||||
|
class NoTerminal(Transform):
|
||||||
|
"""remove typical terminal control codes from input"""
|
||||||
|
|
||||||
|
REPLACEMENT_MAP = dict((x, 0x2400 + x) for x in range(32) if unichr(x) not in '\r\n\b\t')
|
||||||
|
REPLACEMENT_MAP.update(
|
||||||
|
{
|
||||||
|
0x7F: 0x2421, # DEL
|
||||||
|
0x9B: 0x2425, # CSI
|
||||||
|
})
|
||||||
|
|
||||||
|
def rx(self, text):
|
||||||
|
return text.translate(self.REPLACEMENT_MAP)
|
||||||
|
|
||||||
|
echo = rx
|
||||||
|
|
||||||
|
|
||||||
|
class NoControls(NoTerminal):
|
||||||
|
"""Remove all control codes, incl. CR+LF"""
|
||||||
|
|
||||||
|
REPLACEMENT_MAP = dict((x, 0x2400 + x) for x in range(32))
|
||||||
|
REPLACEMENT_MAP.update(
|
||||||
|
{
|
||||||
|
0x20: 0x2423, # visual space
|
||||||
|
0x7F: 0x2421, # DEL
|
||||||
|
0x9B: 0x2425, # CSI
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
class Printable(Transform):
|
||||||
|
"""Show decimal code for all non-ASCII characters and replace most control codes"""
|
||||||
|
|
||||||
|
def rx(self, text):
|
||||||
|
r = []
|
||||||
|
for c in text:
|
||||||
|
if ' ' <= c < '\x7f' or c in '\r\n\b\t':
|
||||||
|
r.append(c)
|
||||||
|
elif c < ' ':
|
||||||
|
r.append(unichr(0x2400 + ord(c)))
|
||||||
|
else:
|
||||||
|
r.extend(unichr(0x2080 + ord(d) - 48) for d in '{:d}'.format(ord(c)))
|
||||||
|
r.append(' ')
|
||||||
|
return ''.join(r)
|
||||||
|
|
||||||
|
echo = rx
|
||||||
|
|
||||||
|
|
||||||
|
class Colorize(Transform):
|
||||||
|
"""Apply different colors for received and echo"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
# XXX make it configurable, use colorama?
|
||||||
|
self.input_color = '\x1b[37m'
|
||||||
|
self.echo_color = '\x1b[31m'
|
||||||
|
|
||||||
|
def rx(self, text):
|
||||||
|
return self.input_color + text
|
||||||
|
|
||||||
|
def echo(self, text):
|
||||||
|
return self.echo_color + text
|
||||||
|
|
||||||
|
|
||||||
|
class DebugIO(Transform):
|
||||||
|
"""Print what is sent and received"""
|
||||||
|
|
||||||
|
def rx(self, text):
|
||||||
|
sys.stderr.write(' [RX:{}] '.format(repr(text)))
|
||||||
|
sys.stderr.flush()
|
||||||
|
return text
|
||||||
|
|
||||||
|
def tx(self, text):
|
||||||
|
sys.stderr.write(' [TX:{}] '.format(repr(text)))
|
||||||
|
sys.stderr.flush()
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
# other ideas:
|
||||||
|
# - add date/time for each newline
|
||||||
|
# - insert newline after: a) timeout b) packet end character
|
||||||
|
|
||||||
|
EOL_TRANSFORMATIONS = {
|
||||||
|
'crlf': CRLF,
|
||||||
|
'cr': CR,
|
||||||
|
'lf': LF,
|
||||||
|
}
|
||||||
|
|
||||||
|
TRANSFORMATIONS = {
|
||||||
|
'direct': Transform, # no transformation
|
||||||
|
'default': NoTerminal,
|
||||||
|
'nocontrol': NoControls,
|
||||||
|
'printable': Printable,
|
||||||
|
'colorize': Colorize,
|
||||||
|
'debug': DebugIO,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
def ask_for_port():
|
||||||
|
"""\
|
||||||
|
Show a list of ports and ask the user for a choice. To make selection
|
||||||
|
easier on systems with long device names, also allow the input of an
|
||||||
|
index.
|
||||||
|
"""
|
||||||
|
sys.stderr.write('\n--- Available ports:\n')
|
||||||
|
ports = []
|
||||||
|
for n, (port, desc, hwid) in enumerate(sorted(comports()), 1):
|
||||||
|
sys.stderr.write('--- {:2}: {:20} {!r}\n'.format(n, port, desc))
|
||||||
|
ports.append(port)
|
||||||
|
while True:
|
||||||
|
port = raw_input('--- Enter port index or full name: ')
|
||||||
|
try:
|
||||||
|
index = int(port) - 1
|
||||||
|
if not 0 <= index < len(ports):
|
||||||
|
sys.stderr.write('--- Invalid index!\n')
|
||||||
|
continue
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
port = ports[index]
|
||||||
|
return port
|
||||||
|
|
||||||
|
|
||||||
|
class Miniterm(object):
|
||||||
|
"""\
|
||||||
|
Terminal application. Copy data from serial port to console and vice versa.
|
||||||
|
Handle special keys from the console to show menu etc.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, serial_instance, echo=False, eol='crlf', filters=()):
|
||||||
|
self.console = Console()
|
||||||
|
self.serial = serial_instance
|
||||||
|
self.echo = echo
|
||||||
|
self.raw = False
|
||||||
|
self.input_encoding = 'UTF-8'
|
||||||
|
self.output_encoding = 'UTF-8'
|
||||||
|
self.eol = eol
|
||||||
|
self.filters = filters
|
||||||
|
self.update_transformations()
|
||||||
|
self.exit_character = 0x1d # GS/CTRL+]
|
||||||
|
self.menu_character = 0x14 # Menu: CTRL+T
|
||||||
|
self.alive = None
|
||||||
|
self._reader_alive = None
|
||||||
|
self.receiver_thread = None
|
||||||
|
self.rx_decoder = None
|
||||||
|
self.tx_decoder = None
|
||||||
|
|
||||||
|
def _start_reader(self):
|
||||||
|
"""Start reader thread"""
|
||||||
|
self._reader_alive = True
|
||||||
|
# start serial->console thread
|
||||||
|
self.receiver_thread = threading.Thread(target=self.reader, name='rx')
|
||||||
|
self.receiver_thread.daemon = True
|
||||||
|
self.receiver_thread.start()
|
||||||
|
|
||||||
|
def _stop_reader(self):
|
||||||
|
"""Stop reader thread only, wait for clean exit of thread"""
|
||||||
|
self._reader_alive = False
|
||||||
|
if hasattr(self.serial, 'cancel_read'):
|
||||||
|
self.serial.cancel_read()
|
||||||
|
self.receiver_thread.join()
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
"""start worker threads"""
|
||||||
|
self.alive = True
|
||||||
|
self._start_reader()
|
||||||
|
# enter console->serial loop
|
||||||
|
self.transmitter_thread = threading.Thread(target=self.writer, name='tx')
|
||||||
|
self.transmitter_thread.daemon = True
|
||||||
|
self.transmitter_thread.start()
|
||||||
|
self.console.setup()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
"""set flag to stop worker threads"""
|
||||||
|
self.alive = False
|
||||||
|
|
||||||
|
def join(self, transmit_only=False):
|
||||||
|
"""wait for worker threads to terminate"""
|
||||||
|
self.transmitter_thread.join()
|
||||||
|
if not transmit_only:
|
||||||
|
if hasattr(self.serial, 'cancel_read'):
|
||||||
|
self.serial.cancel_read()
|
||||||
|
self.receiver_thread.join()
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.serial.close()
|
||||||
|
|
||||||
|
def update_transformations(self):
|
||||||
|
"""take list of transformation classes and instantiate them for rx and tx"""
|
||||||
|
transformations = [EOL_TRANSFORMATIONS[self.eol]] + [TRANSFORMATIONS[f]
|
||||||
|
for f in self.filters]
|
||||||
|
self.tx_transformations = [t() for t in transformations]
|
||||||
|
self.rx_transformations = list(reversed(self.tx_transformations))
|
||||||
|
|
||||||
|
def set_rx_encoding(self, encoding, errors='replace'):
|
||||||
|
"""set encoding for received data"""
|
||||||
|
self.input_encoding = encoding
|
||||||
|
self.rx_decoder = codecs.getincrementaldecoder(encoding)(errors)
|
||||||
|
|
||||||
|
def set_tx_encoding(self, encoding, errors='replace'):
|
||||||
|
"""set encoding for transmitted data"""
|
||||||
|
self.output_encoding = encoding
|
||||||
|
self.tx_encoder = codecs.getincrementalencoder(encoding)(errors)
|
||||||
|
|
||||||
|
def dump_port_settings(self):
|
||||||
|
"""Write current settings to sys.stderr"""
|
||||||
|
sys.stderr.write("\n--- Settings: {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits}\n".format(
|
||||||
|
p=self.serial))
|
||||||
|
sys.stderr.write('--- RTS: {:8} DTR: {:8} BREAK: {:8}\n'.format(
|
||||||
|
('active' if self.serial.rts else 'inactive'),
|
||||||
|
('active' if self.serial.dtr else 'inactive'),
|
||||||
|
('active' if self.serial.break_condition else 'inactive')))
|
||||||
|
try:
|
||||||
|
sys.stderr.write('--- CTS: {:8} DSR: {:8} RI: {:8} CD: {:8}\n'.format(
|
||||||
|
('active' if self.serial.cts else 'inactive'),
|
||||||
|
('active' if self.serial.dsr else 'inactive'),
|
||||||
|
('active' if self.serial.ri else 'inactive'),
|
||||||
|
('active' if self.serial.cd else 'inactive')))
|
||||||
|
except serial.SerialException:
|
||||||
|
# on RFC 2217 ports, it can happen if no modem state notification was
|
||||||
|
# yet received. ignore this error.
|
||||||
|
pass
|
||||||
|
sys.stderr.write('--- software flow control: {}\n'.format('active' if self.serial.xonxoff else 'inactive'))
|
||||||
|
sys.stderr.write('--- hardware flow control: {}\n'.format('active' if self.serial.rtscts else 'inactive'))
|
||||||
|
sys.stderr.write('--- serial input encoding: {}\n'.format(self.input_encoding))
|
||||||
|
sys.stderr.write('--- serial output encoding: {}\n'.format(self.output_encoding))
|
||||||
|
sys.stderr.write('--- EOL: {}\n'.format(self.eol.upper()))
|
||||||
|
sys.stderr.write('--- filters: {}\n'.format(' '.join(self.filters)))
|
||||||
|
|
||||||
|
def reader(self):
|
||||||
|
"""loop and copy serial->console"""
|
||||||
|
try:
|
||||||
|
while self.alive and self._reader_alive:
|
||||||
|
# read all that is there or wait for one byte
|
||||||
|
data = self.serial.read(self.serial.in_waiting or 1)
|
||||||
|
if data:
|
||||||
|
if self.raw:
|
||||||
|
self.console.write_bytes(data)
|
||||||
|
else:
|
||||||
|
text = self.rx_decoder.decode(data)
|
||||||
|
for transformation in self.rx_transformations:
|
||||||
|
text = transformation.rx(text)
|
||||||
|
self.console.write(text)
|
||||||
|
except serial.SerialException:
|
||||||
|
self.alive = False
|
||||||
|
self.console.cancel()
|
||||||
|
raise # XXX handle instead of re-raise?
|
||||||
|
|
||||||
|
def writer(self):
|
||||||
|
"""\
|
||||||
|
Loop and copy console->serial until self.exit_character character is
|
||||||
|
found. When self.menu_character is found, interpret the next key
|
||||||
|
locally.
|
||||||
|
"""
|
||||||
|
menu_active = False
|
||||||
|
try:
|
||||||
|
while self.alive:
|
||||||
|
try:
|
||||||
|
c = self.console.getkey()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
c = '\x03'
|
||||||
|
if not self.alive:
|
||||||
|
break
|
||||||
|
if menu_active:
|
||||||
|
self.handle_menu_key(c)
|
||||||
|
menu_active = False
|
||||||
|
elif c == self.menu_character:
|
||||||
|
menu_active = True # next char will be for menu
|
||||||
|
elif c == self.exit_character:
|
||||||
|
self.stop() # exit app
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
#~ if self.raw:
|
||||||
|
text = c
|
||||||
|
for transformation in self.tx_transformations:
|
||||||
|
text = transformation.tx(text)
|
||||||
|
self.serial.write(self.tx_encoder.encode(text))
|
||||||
|
if self.echo:
|
||||||
|
echo_text = c
|
||||||
|
for transformation in self.tx_transformations:
|
||||||
|
echo_text = transformation.echo(echo_text)
|
||||||
|
self.console.write(echo_text)
|
||||||
|
except:
|
||||||
|
self.alive = False
|
||||||
|
raise
|
||||||
|
|
||||||
|
def handle_menu_key(self, c):
|
||||||
|
"""Implement a simple menu / settings"""
|
||||||
|
if c == self.menu_character or c == self.exit_character:
|
||||||
|
# Menu/exit character again -> send itself
|
||||||
|
self.serial.write(self.tx_encoder.encode(c))
|
||||||
|
if self.echo:
|
||||||
|
self.console.write(c)
|
||||||
|
elif c == '\x15': # CTRL+U -> upload file
|
||||||
|
self.upload_file()
|
||||||
|
elif c in '\x08hH?': # CTRL+H, h, H, ? -> Show help
|
||||||
|
sys.stderr.write(self.get_help_text())
|
||||||
|
elif c == '\x12': # CTRL+R -> Toggle RTS
|
||||||
|
self.serial.rts = not self.serial.rts
|
||||||
|
sys.stderr.write('--- RTS {} ---\n'.format('active' if self.serial.rts else 'inactive'))
|
||||||
|
elif c == '\x04': # CTRL+D -> Toggle DTR
|
||||||
|
self.serial.dtr = not self.serial.dtr
|
||||||
|
sys.stderr.write('--- DTR {} ---\n'.format('active' if self.serial.dtr else 'inactive'))
|
||||||
|
elif c == '\x02': # CTRL+B -> toggle BREAK condition
|
||||||
|
self.serial.break_condition = not self.serial.break_condition
|
||||||
|
sys.stderr.write('--- BREAK {} ---\n'.format('active' if self.serial.break_condition else 'inactive'))
|
||||||
|
elif c == '\x05': # CTRL+E -> toggle local echo
|
||||||
|
self.echo = not self.echo
|
||||||
|
sys.stderr.write('--- local echo {} ---\n'.format('active' if self.echo else 'inactive'))
|
||||||
|
elif c == '\x06': # CTRL+F -> edit filters
|
||||||
|
self.change_filter()
|
||||||
|
elif c == '\x0c': # CTRL+L -> EOL mode
|
||||||
|
modes = list(EOL_TRANSFORMATIONS) # keys
|
||||||
|
eol = modes.index(self.eol) + 1
|
||||||
|
if eol >= len(modes):
|
||||||
|
eol = 0
|
||||||
|
self.eol = modes[eol]
|
||||||
|
sys.stderr.write('--- EOL: {} ---\n'.format(self.eol.upper()))
|
||||||
|
self.update_transformations()
|
||||||
|
elif c == '\x01': # CTRL+A -> set encoding
|
||||||
|
self.change_encoding()
|
||||||
|
elif c == '\x09': # CTRL+I -> info
|
||||||
|
self.dump_port_settings()
|
||||||
|
#~ elif c == '\x01': # CTRL+A -> cycle escape mode
|
||||||
|
#~ elif c == '\x0c': # CTRL+L -> cycle linefeed mode
|
||||||
|
elif c in 'pP': # P -> change port
|
||||||
|
self.change_port()
|
||||||
|
elif c in 'sS': # S -> suspend / open port temporarily
|
||||||
|
self.suspend_port()
|
||||||
|
elif c in 'bB': # B -> change baudrate
|
||||||
|
self.change_baudrate()
|
||||||
|
elif c == '8': # 8 -> change to 8 bits
|
||||||
|
self.serial.bytesize = serial.EIGHTBITS
|
||||||
|
self.dump_port_settings()
|
||||||
|
elif c == '7': # 7 -> change to 8 bits
|
||||||
|
self.serial.bytesize = serial.SEVENBITS
|
||||||
|
self.dump_port_settings()
|
||||||
|
elif c in 'eE': # E -> change to even parity
|
||||||
|
self.serial.parity = serial.PARITY_EVEN
|
||||||
|
self.dump_port_settings()
|
||||||
|
elif c in 'oO': # O -> change to odd parity
|
||||||
|
self.serial.parity = serial.PARITY_ODD
|
||||||
|
self.dump_port_settings()
|
||||||
|
elif c in 'mM': # M -> change to mark parity
|
||||||
|
self.serial.parity = serial.PARITY_MARK
|
||||||
|
self.dump_port_settings()
|
||||||
|
elif c in 'sS': # S -> change to space parity
|
||||||
|
self.serial.parity = serial.PARITY_SPACE
|
||||||
|
self.dump_port_settings()
|
||||||
|
elif c in 'nN': # N -> change to no parity
|
||||||
|
self.serial.parity = serial.PARITY_NONE
|
||||||
|
self.dump_port_settings()
|
||||||
|
elif c == '1': # 1 -> change to 1 stop bits
|
||||||
|
self.serial.stopbits = serial.STOPBITS_ONE
|
||||||
|
self.dump_port_settings()
|
||||||
|
elif c == '2': # 2 -> change to 2 stop bits
|
||||||
|
self.serial.stopbits = serial.STOPBITS_TWO
|
||||||
|
self.dump_port_settings()
|
||||||
|
elif c == '3': # 3 -> change to 1.5 stop bits
|
||||||
|
self.serial.stopbits = serial.STOPBITS_ONE_POINT_FIVE
|
||||||
|
self.dump_port_settings()
|
||||||
|
elif c in 'xX': # X -> change software flow control
|
||||||
|
self.serial.xonxoff = (c == 'X')
|
||||||
|
self.dump_port_settings()
|
||||||
|
elif c in 'rR': # R -> change hardware flow control
|
||||||
|
self.serial.rtscts = (c == 'R')
|
||||||
|
self.dump_port_settings()
|
||||||
|
else:
|
||||||
|
sys.stderr.write('--- unknown menu character {} --\n'.format(key_description(c)))
|
||||||
|
|
||||||
|
def upload_file(self):
|
||||||
|
"""Ask user for filenname and send its contents"""
|
||||||
|
sys.stderr.write('\n--- File to upload: ')
|
||||||
|
sys.stderr.flush()
|
||||||
|
with self.console:
|
||||||
|
filename = sys.stdin.readline().rstrip('\r\n')
|
||||||
|
if filename:
|
||||||
|
try:
|
||||||
|
with open(filename, 'rb') as f:
|
||||||
|
sys.stderr.write('--- Sending file {} ---\n'.format(filename))
|
||||||
|
while True:
|
||||||
|
block = f.read(1024)
|
||||||
|
if not block:
|
||||||
|
break
|
||||||
|
self.serial.write(block)
|
||||||
|
# Wait for output buffer to drain.
|
||||||
|
self.serial.flush()
|
||||||
|
sys.stderr.write('.') # Progress indicator.
|
||||||
|
sys.stderr.write('\n--- File {} sent ---\n'.format(filename))
|
||||||
|
except IOError as e:
|
||||||
|
sys.stderr.write('--- ERROR opening file {}: {} ---\n'.format(filename, e))
|
||||||
|
|
||||||
|
def change_filter(self):
|
||||||
|
"""change the i/o transformations"""
|
||||||
|
sys.stderr.write('\n--- Available Filters:\n')
|
||||||
|
sys.stderr.write('\n'.join(
|
||||||
|
'--- {:<10} = {.__doc__}'.format(k, v)
|
||||||
|
for k, v in sorted(TRANSFORMATIONS.items())))
|
||||||
|
sys.stderr.write('\n--- Enter new filter name(s) [{}]: '.format(' '.join(self.filters)))
|
||||||
|
with self.console:
|
||||||
|
new_filters = sys.stdin.readline().lower().split()
|
||||||
|
if new_filters:
|
||||||
|
for f in new_filters:
|
||||||
|
if f not in TRANSFORMATIONS:
|
||||||
|
sys.stderr.write('--- unknown filter: {}\n'.format(repr(f)))
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
self.filters = new_filters
|
||||||
|
self.update_transformations()
|
||||||
|
sys.stderr.write('--- filters: {}\n'.format(' '.join(self.filters)))
|
||||||
|
|
||||||
|
def change_encoding(self):
|
||||||
|
"""change encoding on the serial port"""
|
||||||
|
sys.stderr.write('\n--- Enter new encoding name [{}]: '.format(self.input_encoding))
|
||||||
|
with self.console:
|
||||||
|
new_encoding = sys.stdin.readline().strip()
|
||||||
|
if new_encoding:
|
||||||
|
try:
|
||||||
|
codecs.lookup(new_encoding)
|
||||||
|
except LookupError:
|
||||||
|
sys.stderr.write('--- invalid encoding name: {}\n'.format(new_encoding))
|
||||||
|
else:
|
||||||
|
self.set_rx_encoding(new_encoding)
|
||||||
|
self.set_tx_encoding(new_encoding)
|
||||||
|
sys.stderr.write('--- serial input encoding: {}\n'.format(self.input_encoding))
|
||||||
|
sys.stderr.write('--- serial output encoding: {}\n'.format(self.output_encoding))
|
||||||
|
|
||||||
|
def change_baudrate(self):
|
||||||
|
"""change the baudrate"""
|
||||||
|
sys.stderr.write('\n--- Baudrate: ')
|
||||||
|
sys.stderr.flush()
|
||||||
|
with self.console:
|
||||||
|
backup = self.serial.baudrate
|
||||||
|
try:
|
||||||
|
self.serial.baudrate = int(sys.stdin.readline().strip())
|
||||||
|
except ValueError as e:
|
||||||
|
sys.stderr.write('--- ERROR setting baudrate: {} ---\n'.format(e))
|
||||||
|
self.serial.baudrate = backup
|
||||||
|
else:
|
||||||
|
self.dump_port_settings()
|
||||||
|
|
||||||
|
def change_port(self):
|
||||||
|
"""Have a conversation with the user to change the serial port"""
|
||||||
|
with self.console:
|
||||||
|
try:
|
||||||
|
port = ask_for_port()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
port = None
|
||||||
|
if port and port != self.serial.port:
|
||||||
|
# reader thread needs to be shut down
|
||||||
|
self._stop_reader()
|
||||||
|
# save settings
|
||||||
|
settings = self.serial.getSettingsDict()
|
||||||
|
try:
|
||||||
|
new_serial = serial.serial_for_url(port, do_not_open=True)
|
||||||
|
# restore settings and open
|
||||||
|
new_serial.applySettingsDict(settings)
|
||||||
|
new_serial.rts = self.serial.rts
|
||||||
|
new_serial.dtr = self.serial.dtr
|
||||||
|
new_serial.open()
|
||||||
|
new_serial.break_condition = self.serial.break_condition
|
||||||
|
except Exception as e:
|
||||||
|
sys.stderr.write('--- ERROR opening new port: {} ---\n'.format(e))
|
||||||
|
new_serial.close()
|
||||||
|
else:
|
||||||
|
self.serial.close()
|
||||||
|
self.serial = new_serial
|
||||||
|
sys.stderr.write('--- Port changed to: {} ---\n'.format(self.serial.port))
|
||||||
|
# and restart the reader thread
|
||||||
|
self._start_reader()
|
||||||
|
|
||||||
|
def suspend_port(self):
|
||||||
|
"""\
|
||||||
|
open port temporarily, allow reconnect, exit and port change to get
|
||||||
|
out of the loop
|
||||||
|
"""
|
||||||
|
# reader thread needs to be shut down
|
||||||
|
self._stop_reader()
|
||||||
|
self.serial.close()
|
||||||
|
sys.stderr.write('\n--- Port closed: {} ---\n'.format(self.serial.port))
|
||||||
|
do_change_port = False
|
||||||
|
while not self.serial.is_open:
|
||||||
|
sys.stderr.write('--- Quit: {exit} | p: port change | any other key to reconnect ---\n'.format(
|
||||||
|
exit=key_description(self.exit_character)))
|
||||||
|
k = self.console.getkey()
|
||||||
|
if k == self.exit_character:
|
||||||
|
self.stop() # exit app
|
||||||
|
break
|
||||||
|
elif k in 'pP':
|
||||||
|
do_change_port = True
|
||||||
|
break
|
||||||
|
try:
|
||||||
|
self.serial.open()
|
||||||
|
except Exception as e:
|
||||||
|
sys.stderr.write('--- ERROR opening port: {} ---\n'.format(e))
|
||||||
|
if do_change_port:
|
||||||
|
self.change_port()
|
||||||
|
else:
|
||||||
|
# and restart the reader thread
|
||||||
|
self._start_reader()
|
||||||
|
sys.stderr.write('--- Port opened: {} ---\n'.format(self.serial.port))
|
||||||
|
|
||||||
|
def get_help_text(self):
|
||||||
|
"""return the help text"""
|
||||||
|
# help text, starts with blank line!
|
||||||
|
return """
|
||||||
|
--- pySerial ({version}) - miniterm - help
|
||||||
|
---
|
||||||
|
--- {exit:8} Exit program
|
||||||
|
--- {menu:8} Menu escape key, followed by:
|
||||||
|
--- Menu keys:
|
||||||
|
--- {menu:7} Send the menu character itself to remote
|
||||||
|
--- {exit:7} Send the exit character itself to remote
|
||||||
|
--- {info:7} Show info
|
||||||
|
--- {upload:7} Upload file (prompt will be shown)
|
||||||
|
--- {repr:7} encoding
|
||||||
|
--- {filter:7} edit filters
|
||||||
|
--- Toggles:
|
||||||
|
--- {rts:7} RTS {dtr:7} DTR {brk:7} BREAK
|
||||||
|
--- {echo:7} echo {eol:7} EOL
|
||||||
|
---
|
||||||
|
--- Port settings ({menu} followed by the following):
|
||||||
|
--- p change port
|
||||||
|
--- 7 8 set data bits
|
||||||
|
--- N E O S M change parity (None, Even, Odd, Space, Mark)
|
||||||
|
--- 1 2 3 set stop bits (1, 2, 1.5)
|
||||||
|
--- b change baud rate
|
||||||
|
--- x X disable/enable software flow control
|
||||||
|
--- r R disable/enable hardware flow control
|
||||||
|
""".format(version=getattr(serial, 'VERSION', 'unknown version'),
|
||||||
|
exit=key_description(self.exit_character),
|
||||||
|
menu=key_description(self.menu_character),
|
||||||
|
rts=key_description('\x12'),
|
||||||
|
dtr=key_description('\x04'),
|
||||||
|
brk=key_description('\x02'),
|
||||||
|
echo=key_description('\x05'),
|
||||||
|
info=key_description('\x09'),
|
||||||
|
upload=key_description('\x15'),
|
||||||
|
repr=key_description('\x01'),
|
||||||
|
filter=key_description('\x06'),
|
||||||
|
eol=key_description('\x0c'))
|
||||||
|
|
||||||
|
|
||||||
|
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
# default args can be used to override when calling main() from an other script
|
||||||
|
# e.g to create a miniterm-my-device.py
|
||||||
|
def main(default_port=None, default_baudrate=9600, default_rts=None, default_dtr=None):
|
||||||
|
"""Command line tool, entry point"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Miniterm - A simple terminal program for the serial port.")
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"port",
|
||||||
|
nargs='?',
|
||||||
|
help="serial port name ('-' to show port list)",
|
||||||
|
default=default_port)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"baudrate",
|
||||||
|
nargs='?',
|
||||||
|
type=int,
|
||||||
|
help="set baud rate, default: %(default)s",
|
||||||
|
default=default_baudrate)
|
||||||
|
|
||||||
|
group = parser.add_argument_group("port settings")
|
||||||
|
|
||||||
|
group.add_argument(
|
||||||
|
"--parity",
|
||||||
|
choices=['N', 'E', 'O', 'S', 'M'],
|
||||||
|
type=lambda c: c.upper(),
|
||||||
|
help="set parity, one of {N E O S M}, default: N",
|
||||||
|
default='N')
|
||||||
|
|
||||||
|
group.add_argument(
|
||||||
|
"--rtscts",
|
||||||
|
action="store_true",
|
||||||
|
help="enable RTS/CTS flow control (default off)",
|
||||||
|
default=False)
|
||||||
|
|
||||||
|
group.add_argument(
|
||||||
|
"--xonxoff",
|
||||||
|
action="store_true",
|
||||||
|
help="enable software flow control (default off)",
|
||||||
|
default=False)
|
||||||
|
|
||||||
|
group.add_argument(
|
||||||
|
"--rts",
|
||||||
|
type=int,
|
||||||
|
help="set initial RTS line state (possible values: 0, 1)",
|
||||||
|
default=default_rts)
|
||||||
|
|
||||||
|
group.add_argument(
|
||||||
|
"--dtr",
|
||||||
|
type=int,
|
||||||
|
help="set initial DTR line state (possible values: 0, 1)",
|
||||||
|
default=default_dtr)
|
||||||
|
|
||||||
|
group.add_argument(
|
||||||
|
"--ask",
|
||||||
|
action="store_true",
|
||||||
|
help="ask again for port when open fails",
|
||||||
|
default=False)
|
||||||
|
|
||||||
|
group = parser.add_argument_group("data handling")
|
||||||
|
|
||||||
|
group.add_argument(
|
||||||
|
"-e", "--echo",
|
||||||
|
action="store_true",
|
||||||
|
help="enable local echo (default off)",
|
||||||
|
default=False)
|
||||||
|
|
||||||
|
group.add_argument(
|
||||||
|
"--encoding",
|
||||||
|
dest="serial_port_encoding",
|
||||||
|
metavar="CODEC",
|
||||||
|
help="set the encoding for the serial port (e.g. hexlify, Latin1, UTF-8), default: %(default)s",
|
||||||
|
default='UTF-8')
|
||||||
|
|
||||||
|
group.add_argument(
|
||||||
|
"-f", "--filter",
|
||||||
|
action="append",
|
||||||
|
metavar="NAME",
|
||||||
|
help="add text transformation",
|
||||||
|
default=[])
|
||||||
|
|
||||||
|
group.add_argument(
|
||||||
|
"--eol",
|
||||||
|
choices=['CR', 'LF', 'CRLF'],
|
||||||
|
type=lambda c: c.upper(),
|
||||||
|
help="end of line mode",
|
||||||
|
default='CRLF')
|
||||||
|
|
||||||
|
group.add_argument(
|
||||||
|
"--raw",
|
||||||
|
action="store_true",
|
||||||
|
help="Do no apply any encodings/transformations",
|
||||||
|
default=False)
|
||||||
|
|
||||||
|
group = parser.add_argument_group("hotkeys")
|
||||||
|
|
||||||
|
group.add_argument(
|
||||||
|
"--exit-char",
|
||||||
|
type=int,
|
||||||
|
metavar='NUM',
|
||||||
|
help="Unicode of special character that is used to exit the application, default: %(default)s",
|
||||||
|
default=0x1d) # GS/CTRL+]
|
||||||
|
|
||||||
|
group.add_argument(
|
||||||
|
"--menu-char",
|
||||||
|
type=int,
|
||||||
|
metavar='NUM',
|
||||||
|
help="Unicode code of special character that is used to control miniterm (menu), default: %(default)s",
|
||||||
|
default=0x14) # Menu: CTRL+T
|
||||||
|
|
||||||
|
group = parser.add_argument_group("diagnostics")
|
||||||
|
|
||||||
|
group.add_argument(
|
||||||
|
"-q", "--quiet",
|
||||||
|
action="store_true",
|
||||||
|
help="suppress non-error messages",
|
||||||
|
default=False)
|
||||||
|
|
||||||
|
group.add_argument(
|
||||||
|
"--develop",
|
||||||
|
action="store_true",
|
||||||
|
help="show Python traceback on error",
|
||||||
|
default=False)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.menu_char == args.exit_char:
|
||||||
|
parser.error('--exit-char can not be the same as --menu-char')
|
||||||
|
|
||||||
|
if args.filter:
|
||||||
|
if 'help' in args.filter:
|
||||||
|
sys.stderr.write('Available filters:\n')
|
||||||
|
sys.stderr.write('\n'.join(
|
||||||
|
'{:<10} = {.__doc__}'.format(k, v)
|
||||||
|
for k, v in sorted(TRANSFORMATIONS.items())))
|
||||||
|
sys.stderr.write('\n')
|
||||||
|
sys.exit(1)
|
||||||
|
filters = args.filter
|
||||||
|
else:
|
||||||
|
filters = ['default']
|
||||||
|
|
||||||
|
while True:
|
||||||
|
# no port given on command line -> ask user now
|
||||||
|
if args.port is None or args.port == '-':
|
||||||
|
try:
|
||||||
|
args.port = ask_for_port()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
sys.stderr.write('\n')
|
||||||
|
parser.error('user aborted and port is not given')
|
||||||
|
else:
|
||||||
|
if not args.port:
|
||||||
|
parser.error('port is not given')
|
||||||
|
try:
|
||||||
|
serial_instance = serial.serial_for_url(
|
||||||
|
args.port,
|
||||||
|
args.baudrate,
|
||||||
|
parity=args.parity,
|
||||||
|
rtscts=args.rtscts,
|
||||||
|
xonxoff=args.xonxoff,
|
||||||
|
do_not_open=True)
|
||||||
|
|
||||||
|
if not hasattr(serial_instance, 'cancel_read'):
|
||||||
|
# enable timeout for alive flag polling if cancel_read is not available
|
||||||
|
serial_instance.timeout = 1
|
||||||
|
|
||||||
|
if args.dtr is not None:
|
||||||
|
if not args.quiet:
|
||||||
|
sys.stderr.write('--- forcing DTR {}\n'.format('active' if args.dtr else 'inactive'))
|
||||||
|
serial_instance.dtr = args.dtr
|
||||||
|
if args.rts is not None:
|
||||||
|
if not args.quiet:
|
||||||
|
sys.stderr.write('--- forcing RTS {}\n'.format('active' if args.rts else 'inactive'))
|
||||||
|
serial_instance.rts = args.rts
|
||||||
|
|
||||||
|
serial_instance.open()
|
||||||
|
except serial.SerialException as e:
|
||||||
|
sys.stderr.write('could not open port {}: {}\n'.format(repr(args.port), e))
|
||||||
|
if args.develop:
|
||||||
|
raise
|
||||||
|
if not args.ask:
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
args.port = '-'
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
miniterm = Miniterm(
|
||||||
|
serial_instance,
|
||||||
|
echo=args.echo,
|
||||||
|
eol=args.eol.lower(),
|
||||||
|
filters=filters)
|
||||||
|
miniterm.exit_character = unichr(args.exit_char)
|
||||||
|
miniterm.menu_character = unichr(args.menu_char)
|
||||||
|
miniterm.raw = args.raw
|
||||||
|
miniterm.set_rx_encoding(args.serial_port_encoding)
|
||||||
|
miniterm.set_tx_encoding(args.serial_port_encoding)
|
||||||
|
|
||||||
|
if not args.quiet:
|
||||||
|
sys.stderr.write('--- Miniterm on {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits} ---\n'.format(
|
||||||
|
p=miniterm.serial))
|
||||||
|
sys.stderr.write('--- Quit: {} | Menu: {} | Help: {} followed by {} ---\n'.format(
|
||||||
|
key_description(miniterm.exit_character),
|
||||||
|
key_description(miniterm.menu_character),
|
||||||
|
key_description(miniterm.menu_character),
|
||||||
|
key_description('\x08')))
|
||||||
|
|
||||||
|
miniterm.start()
|
||||||
|
try:
|
||||||
|
miniterm.join(True)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pass
|
||||||
|
if not args.quiet:
|
||||||
|
sys.stderr.write("\n--- exit ---\n")
|
||||||
|
miniterm.join()
|
||||||
|
miniterm.close()
|
||||||
|
|
||||||
|
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
12
venv/bin/pip
Executable file
12
venv/bin/pip
Executable file
@ -0,0 +1,12 @@
|
|||||||
|
#!/home/stephan/PycharmProjects/lopy_beacon/venv/bin/python
|
||||||
|
# EASY-INSTALL-ENTRY-SCRIPT: 'pip==10.0.1','console_scripts','pip'
|
||||||
|
__requires__ = 'pip==10.0.1'
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pkg_resources import load_entry_point
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(
|
||||||
|
load_entry_point('pip==10.0.1', 'console_scripts', 'pip')()
|
||||||
|
)
|
||||||
12
venv/bin/pip3
Executable file
12
venv/bin/pip3
Executable file
@ -0,0 +1,12 @@
|
|||||||
|
#!/home/stephan/PycharmProjects/lopy_beacon/venv/bin/python
|
||||||
|
# EASY-INSTALL-ENTRY-SCRIPT: 'pip==10.0.1','console_scripts','pip3'
|
||||||
|
__requires__ = 'pip==10.0.1'
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pkg_resources import load_entry_point
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(
|
||||||
|
load_entry_point('pip==10.0.1', 'console_scripts', 'pip3')()
|
||||||
|
)
|
||||||
12
venv/bin/pip3.6
Executable file
12
venv/bin/pip3.6
Executable file
@ -0,0 +1,12 @@
|
|||||||
|
#!/home/stephan/PycharmProjects/lopy_beacon/venv/bin/python
|
||||||
|
# EASY-INSTALL-ENTRY-SCRIPT: 'pip==10.0.1','console_scripts','pip3.6'
|
||||||
|
__requires__ = 'pip==10.0.1'
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pkg_resources import load_entry_point
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
||||||
|
sys.exit(
|
||||||
|
load_entry_point('pip==10.0.1', 'console_scripts', 'pip3.6')()
|
||||||
|
)
|
||||||
BIN
venv/bin/python
Executable file
BIN
venv/bin/python
Executable file
Binary file not shown.
BIN
venv/bin/python3
Executable file
BIN
venv/bin/python3
Executable file
Binary file not shown.
BIN
venv/bin/python3.6
Executable file
BIN
venv/bin/python3.6
Executable file
Binary file not shown.
@ -0,0 +1 @@
|
|||||||
|
pip
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
Copyright © 2014 by the Pallets team.
|
||||||
|
|
||||||
|
Some rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms of the software as
|
||||||
|
well as documentation, with or without modification, are permitted
|
||||||
|
provided that the following conditions are met:
|
||||||
|
|
||||||
|
- Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
- Redistributions in binary form must reproduce the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer in the
|
||||||
|
documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
- Neither the name of the copyright holder nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE AND DOCUMENTATION 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 AND DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGE.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
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.
|
||||||
121
venv/lib/python3.6/site-packages/Click-7.0.dist-info/METADATA
Normal file
121
venv/lib/python3.6/site-packages/Click-7.0.dist-info/METADATA
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
Metadata-Version: 2.1
|
||||||
|
Name: Click
|
||||||
|
Version: 7.0
|
||||||
|
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 Team
|
||||||
|
Maintainer-email: contact@palletsprojects.com
|
||||||
|
License: BSD
|
||||||
|
Project-URL: Documentation, https://click.palletsprojects.com/
|
||||||
|
Project-URL: Code, https://github.com/pallets/click
|
||||||
|
Project-URL: Issue tracker, https://github.com/pallets/click/issues
|
||||||
|
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
|
||||||
|
Classifier: Programming Language :: Python :: 2
|
||||||
|
Classifier: Programming Language :: Python :: 2.7
|
||||||
|
Classifier: Programming Language :: Python :: 3
|
||||||
|
Classifier: Programming Language :: Python :: 3.4
|
||||||
|
Classifier: Programming Language :: Python :: 3.5
|
||||||
|
Classifier: Programming Language :: Python :: 3.6
|
||||||
|
Classifier: Programming Language :: Python :: 3.7
|
||||||
|
Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
|
||||||
|
|
||||||
|
\$ 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 click
|
||||||
|
|
||||||
|
Click supports Python 3.4 and newer, Python 2.7, and PyPy.
|
||||||
|
|
||||||
|
.. _pip: https://pip.pypa.io/en/stable/quickstart/
|
||||||
|
|
||||||
|
|
||||||
|
A Simple Example
|
||||||
|
----------------
|
||||||
|
|
||||||
|
What does it look like? Here is an example of a simple Click program:
|
||||||
|
|
||||||
|
.. 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("Hello, %s!" % name)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
hello()
|
||||||
|
|
||||||
|
And what it looks like when run:
|
||||||
|
|
||||||
|
.. 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
|
||||||
|
-----
|
||||||
|
|
||||||
|
* Website: https://palletsprojects.com/p/click/
|
||||||
|
* Documentation: https://click.palletsprojects.com/
|
||||||
|
* License: `BSD <https://github.com/pallets/click/blob/master/LICENSE.rst>`_
|
||||||
|
* Releases: https://pypi.org/project/click/
|
||||||
|
* Code: https://github.com/pallets/click
|
||||||
|
* Issue tracker: https://github.com/pallets/click/issues
|
||||||
|
* Test status:
|
||||||
|
|
||||||
|
* Linux, Mac: https://travis-ci.org/pallets/click
|
||||||
|
* Windows: https://ci.appveyor.com/project/pallets/click
|
||||||
|
|
||||||
|
* Test coverage: https://codecov.io/gh/pallets/click
|
||||||
|
|
||||||
|
|
||||||
40
venv/lib/python3.6/site-packages/Click-7.0.dist-info/RECORD
Normal file
40
venv/lib/python3.6/site-packages/Click-7.0.dist-info/RECORD
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
Click-7.0.dist-info/LICENSE.txt,sha256=4hIxn676T0Wcisk3_chVcECjyrivKTZsoqSNI5AlIlw,1876
|
||||||
|
Click-7.0.dist-info/METADATA,sha256=-r8jeke3Zer4diRvT1MjFZuiJ6yTT_qFP39svLqdaLI,3516
|
||||||
|
Click-7.0.dist-info/RECORD,,
|
||||||
|
Click-7.0.dist-info/WHEEL,sha256=gduuPyBvFJQSQ0zdyxF7k0zynDXbIbvg5ZBHoXum5uk,110
|
||||||
|
Click-7.0.dist-info/top_level.txt,sha256=J1ZQogalYS4pphY_lPECoNMfw0HzTSrZglC4Yfwo4xA,6
|
||||||
|
click/__init__.py,sha256=HjGThQ7tef9kkwCV371TBnrf0SAi6fKfU_jtEnbYTvQ,2789
|
||||||
|
click/_bashcomplete.py,sha256=iaNUmtxag0YPfxba3TDYCNietiTMQIrvhRLj-H8okFU,11014
|
||||||
|
click/_compat.py,sha256=vYmvoj4opPxo-c-2GMQQjYT_r_QkOKybkfGoeVrt0dA,23399
|
||||||
|
click/_termui_impl.py,sha256=xHmLtOJhKUCVD6168yucJ9fknUJPAMs0eUTPgVUO-GQ,19611
|
||||||
|
click/_textwrap.py,sha256=gwS4m7bdQiJnzaDG8osFcRb-5vn4t4l2qSCy-5csCEc,1198
|
||||||
|
click/_unicodefun.py,sha256=QHy2_5jYlX-36O-JVrTHNnHOqg8tquUR0HmQFev7Ics,4364
|
||||||
|
click/_winconsole.py,sha256=PPWVak8Iikm_gAPsxMrzwsVFCvHgaW3jPaDWZ1JBl3U,8965
|
||||||
|
click/core.py,sha256=q8FLcDZsagBGSRe5Y9Hi_FGvAeZvusNfoO5EkhkSQ8Y,75305
|
||||||
|
click/decorators.py,sha256=idKt6duLUUfAFftrHoREi8MJSd39XW36pUVHthdglwk,11226
|
||||||
|
click/exceptions.py,sha256=CNpAjBAE7qjaV4WChxQeak95e5yUOau8AsvT-8m6wss,7663
|
||||||
|
click/formatting.py,sha256=eh-cypTUAhpI3HD-K4ZpR3vCiURIO62xXvKkR3tNUTM,8889
|
||||||
|
click/globals.py,sha256=oQkou3ZQ5DgrbVM6BwIBirwiqozbjfirzsLGAlLRRdg,1514
|
||||||
|
click/parser.py,sha256=m-nGZz4VwprM42_qtFlWFGo7yRJQxkBlRcZodoH593Y,15510
|
||||||
|
click/termui.py,sha256=o_ZXB2jyvL2Rce7P_bFGq452iyBq9ykJyRApIPMCZO0,23207
|
||||||
|
click/testing.py,sha256=aYGqY_iWLu2p4k7lkuJ6t3fqpf6aPGqTsyLzNY_ngKg,13062
|
||||||
|
click/types.py,sha256=2Q929p-aBP_ZYuMFJqJR-Ipucofv3fmDc5JzBDPmzJU,23287
|
||||||
|
click/utils.py,sha256=6-D0WkAxvv9FkgHXSHwDIv0l9Gdx9Mm6Z5vuKNLIfZI,15763
|
||||||
|
Click-7.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||||
|
click/__pycache__/parser.cpython-36.pyc,,
|
||||||
|
click/__pycache__/_termui_impl.cpython-36.pyc,,
|
||||||
|
click/__pycache__/testing.cpython-36.pyc,,
|
||||||
|
click/__pycache__/core.cpython-36.pyc,,
|
||||||
|
click/__pycache__/globals.cpython-36.pyc,,
|
||||||
|
click/__pycache__/_compat.cpython-36.pyc,,
|
||||||
|
click/__pycache__/_winconsole.cpython-36.pyc,,
|
||||||
|
click/__pycache__/utils.cpython-36.pyc,,
|
||||||
|
click/__pycache__/_textwrap.cpython-36.pyc,,
|
||||||
|
click/__pycache__/termui.cpython-36.pyc,,
|
||||||
|
click/__pycache__/formatting.cpython-36.pyc,,
|
||||||
|
click/__pycache__/__init__.cpython-36.pyc,,
|
||||||
|
click/__pycache__/_bashcomplete.cpython-36.pyc,,
|
||||||
|
click/__pycache__/_unicodefun.cpython-36.pyc,,
|
||||||
|
click/__pycache__/decorators.cpython-36.pyc,,
|
||||||
|
click/__pycache__/types.cpython-36.pyc,,
|
||||||
|
click/__pycache__/exceptions.cpython-36.pyc,,
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
Wheel-Version: 1.0
|
||||||
|
Generator: bdist_wheel (0.31.1)
|
||||||
|
Root-Is-Purelib: true
|
||||||
|
Tag: py2-none-any
|
||||||
|
Tag: py3-none-any
|
||||||
|
|
||||||
@ -0,0 +1 @@
|
|||||||
|
click
|
||||||
@ -0,0 +1 @@
|
|||||||
|
pip
|
||||||
@ -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.
|
||||||
@ -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.
|
||||||
|
|
||||||
|
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
ampy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||||
|
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
|
||||||
|
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/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
|
||||||
|
adafruit_ampy-1.0.7.dist-info/RECORD,,
|
||||||
|
../../../bin/ampy,sha256=BkB1kUso2KJZsT3Z9JHDxoLz066DbglfKvSLDPY8gNw,250
|
||||||
|
adafruit_ampy-1.0.7.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||||
|
ampy/__pycache__/cli.cpython-36.pyc,,
|
||||||
|
ampy/__pycache__/__init__.cpython-36.pyc,,
|
||||||
|
ampy/__pycache__/pyboard.cpython-36.pyc,,
|
||||||
|
ampy/__pycache__/files.cpython-36.pyc,,
|
||||||
@ -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/python3.6/site-packages/ampy/__init__.py
Normal file
0
venv/lib/python3.6/site-packages/ampy/__init__.py
Normal file
429
venv/lib/python3.6/site-packages/ampy/cli.py
Normal file
429
venv/lib/python3.6/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/python3.6/site-packages/ampy/files.py
Normal file
310
venv/lib/python3.6/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/python3.6/site-packages/ampy/pyboard.py
Normal file
343
venv/lib/python3.6/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()
|
||||||
97
venv/lib/python3.6/site-packages/click/__init__.py
Normal file
97
venv/lib/python3.6/site-packages/click/__init__.py
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
click
|
||||||
|
~~~~~
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
:copyright: © 2014 by the Pallets team.
|
||||||
|
:license: BSD, see LICENSE.rst for more details.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Core classes
|
||||||
|
from .core import Context, BaseCommand, Command, MultiCommand, Group, \
|
||||||
|
CommandCollection, Parameter, Option, Argument
|
||||||
|
|
||||||
|
# Globals
|
||||||
|
from .globals import get_current_context
|
||||||
|
|
||||||
|
# Decorators
|
||||||
|
from .decorators import pass_context, pass_obj, make_pass_decorator, \
|
||||||
|
command, group, argument, option, confirmation_option, \
|
||||||
|
password_option, version_option, help_option
|
||||||
|
|
||||||
|
# Types
|
||||||
|
from .types import ParamType, File, Path, Choice, IntRange, Tuple, \
|
||||||
|
DateTime, STRING, INT, FLOAT, BOOL, UUID, UNPROCESSED, FloatRange
|
||||||
|
|
||||||
|
# Utilities
|
||||||
|
from .utils import echo, get_binary_stream, get_text_stream, open_file, \
|
||||||
|
format_filename, get_app_dir, get_os_args
|
||||||
|
|
||||||
|
# Terminal functions
|
||||||
|
from .termui import prompt, confirm, get_terminal_size, echo_via_pager, \
|
||||||
|
progressbar, clear, style, unstyle, secho, edit, launch, getchar, \
|
||||||
|
pause
|
||||||
|
|
||||||
|
# Exceptions
|
||||||
|
from .exceptions import ClickException, UsageError, BadParameter, \
|
||||||
|
FileError, Abort, NoSuchOption, BadOptionUsage, BadArgumentUsage, \
|
||||||
|
MissingParameter
|
||||||
|
|
||||||
|
# Formatting
|
||||||
|
from .formatting import HelpFormatter, wrap_text
|
||||||
|
|
||||||
|
# Parsing
|
||||||
|
from .parser import OptionParser
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
# Core classes
|
||||||
|
'Context', 'BaseCommand', 'Command', 'MultiCommand', 'Group',
|
||||||
|
'CommandCollection', 'Parameter', 'Option', 'Argument',
|
||||||
|
|
||||||
|
# Globals
|
||||||
|
'get_current_context',
|
||||||
|
|
||||||
|
# Decorators
|
||||||
|
'pass_context', 'pass_obj', 'make_pass_decorator', 'command', 'group',
|
||||||
|
'argument', 'option', 'confirmation_option', 'password_option',
|
||||||
|
'version_option', 'help_option',
|
||||||
|
|
||||||
|
# Types
|
||||||
|
'ParamType', 'File', 'Path', 'Choice', 'IntRange', 'Tuple',
|
||||||
|
'DateTime', 'STRING', 'INT', 'FLOAT', 'BOOL', 'UUID', 'UNPROCESSED',
|
||||||
|
'FloatRange',
|
||||||
|
|
||||||
|
# Utilities
|
||||||
|
'echo', 'get_binary_stream', 'get_text_stream', 'open_file',
|
||||||
|
'format_filename', 'get_app_dir', 'get_os_args',
|
||||||
|
|
||||||
|
# Terminal functions
|
||||||
|
'prompt', 'confirm', 'get_terminal_size', 'echo_via_pager',
|
||||||
|
'progressbar', 'clear', 'style', 'unstyle', 'secho', 'edit', 'launch',
|
||||||
|
'getchar', 'pause',
|
||||||
|
|
||||||
|
# Exceptions
|
||||||
|
'ClickException', 'UsageError', 'BadParameter', 'FileError',
|
||||||
|
'Abort', 'NoSuchOption', 'BadOptionUsage', 'BadArgumentUsage',
|
||||||
|
'MissingParameter',
|
||||||
|
|
||||||
|
# Formatting
|
||||||
|
'HelpFormatter', 'wrap_text',
|
||||||
|
|
||||||
|
# Parsing
|
||||||
|
'OptionParser',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Controls if click should emit the warning about the use of unicode
|
||||||
|
# literals.
|
||||||
|
disable_unicode_literals_warning = False
|
||||||
|
|
||||||
|
|
||||||
|
__version__ = '7.0'
|
||||||
293
venv/lib/python3.6/site-packages/click/_bashcomplete.py
Normal file
293
venv/lib/python3.6/site-packages/click/_bashcomplete.py
Normal file
@ -0,0 +1,293 @@
|
|||||||
|
import copy
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .utils import echo
|
||||||
|
from .parser import split_arg_string
|
||||||
|
from .core import MultiCommand, Option, Argument
|
||||||
|
from .types import Choice
|
||||||
|
|
||||||
|
try:
|
||||||
|
from collections import abc
|
||||||
|
except ImportError:
|
||||||
|
import collections as abc
|
||||||
|
|
||||||
|
WORDBREAK = '='
|
||||||
|
|
||||||
|
# Note, only BASH version 4.4 and later have the nosort option.
|
||||||
|
COMPLETION_SCRIPT_BASH = '''
|
||||||
|
%(complete_func)s() {
|
||||||
|
local IFS=$'\n'
|
||||||
|
COMPREPLY=( $( env COMP_WORDS="${COMP_WORDS[*]}" \\
|
||||||
|
COMP_CWORD=$COMP_CWORD \\
|
||||||
|
%(autocomplete_var)s=complete $1 ) )
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
%(complete_func)setup() {
|
||||||
|
local COMPLETION_OPTIONS=""
|
||||||
|
local BASH_VERSION_ARR=(${BASH_VERSION//./ })
|
||||||
|
# Only BASH version 4.4 and later have the nosort option.
|
||||||
|
if [ ${BASH_VERSION_ARR[0]} -gt 4 ] || ([ ${BASH_VERSION_ARR[0]} -eq 4 ] && [ ${BASH_VERSION_ARR[1]} -ge 4 ]); then
|
||||||
|
COMPLETION_OPTIONS="-o nosort"
|
||||||
|
fi
|
||||||
|
|
||||||
|
complete $COMPLETION_OPTIONS -F %(complete_func)s %(script_names)s
|
||||||
|
}
|
||||||
|
|
||||||
|
%(complete_func)setup
|
||||||
|
'''
|
||||||
|
|
||||||
|
COMPLETION_SCRIPT_ZSH = '''
|
||||||
|
%(complete_func)s() {
|
||||||
|
local -a completions
|
||||||
|
local -a completions_with_descriptions
|
||||||
|
local -a response
|
||||||
|
response=("${(@f)$( env COMP_WORDS=\"${words[*]}\" \\
|
||||||
|
COMP_CWORD=$((CURRENT-1)) \\
|
||||||
|
%(autocomplete_var)s=\"complete_zsh\" \\
|
||||||
|
%(script_names)s )}")
|
||||||
|
|
||||||
|
for key descr in ${(kv)response}; do
|
||||||
|
if [[ "$descr" == "_" ]]; then
|
||||||
|
completions+=("$key")
|
||||||
|
else
|
||||||
|
completions_with_descriptions+=("$key":"$descr")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -n "$completions_with_descriptions" ]; then
|
||||||
|
_describe -V unsorted completions_with_descriptions -U -Q
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$completions" ]; then
|
||||||
|
compadd -U -V unsorted -Q -a completions
|
||||||
|
fi
|
||||||
|
compstate[insert]="automenu"
|
||||||
|
}
|
||||||
|
|
||||||
|
compdef %(complete_func)s %(script_names)s
|
||||||
|
'''
|
||||||
|
|
||||||
|
_invalid_ident_char_re = re.compile(r'[^a-zA-Z0-9_]')
|
||||||
|
|
||||||
|
|
||||||
|
def get_completion_script(prog_name, complete_var, shell):
|
||||||
|
cf_name = _invalid_ident_char_re.sub('', prog_name.replace('-', '_'))
|
||||||
|
script = COMPLETION_SCRIPT_ZSH if shell == 'zsh' else COMPLETION_SCRIPT_BASH
|
||||||
|
return (script % {
|
||||||
|
'complete_func': '_%s_completion' % cf_name,
|
||||||
|
'script_names': prog_name,
|
||||||
|
'autocomplete_var': complete_var,
|
||||||
|
}).strip() + ';'
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_ctx(cli, prog_name, args):
|
||||||
|
"""
|
||||||
|
Parse into a hierarchy of contexts. Contexts are connected through the parent variable.
|
||||||
|
:param cli: command definition
|
||||||
|
:param prog_name: the program that is running
|
||||||
|
:param args: full list of args
|
||||||
|
:return: the final context/command parsed
|
||||||
|
"""
|
||||||
|
ctx = cli.make_context(prog_name, args, resilient_parsing=True)
|
||||||
|
args = ctx.protected_args + ctx.args
|
||||||
|
while args:
|
||||||
|
if isinstance(ctx.command, MultiCommand):
|
||||||
|
if not ctx.command.chain:
|
||||||
|
cmd_name, cmd, args = ctx.command.resolve_command(ctx, args)
|
||||||
|
if cmd is None:
|
||||||
|
return ctx
|
||||||
|
ctx = cmd.make_context(cmd_name, args, parent=ctx,
|
||||||
|
resilient_parsing=True)
|
||||||
|
args = ctx.protected_args + ctx.args
|
||||||
|
else:
|
||||||
|
# Walk chained subcommand contexts saving the last one.
|
||||||
|
while args:
|
||||||
|
cmd_name, cmd, args = ctx.command.resolve_command(ctx, args)
|
||||||
|
if cmd is None:
|
||||||
|
return ctx
|
||||||
|
sub_ctx = cmd.make_context(cmd_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 start_of_option(param_str):
|
||||||
|
"""
|
||||||
|
:param param_str: param_str to check
|
||||||
|
:return: whether or not this is the start of an option declaration (i.e. starts "-" or "--")
|
||||||
|
"""
|
||||||
|
return param_str and param_str[:1] == '-'
|
||||||
|
|
||||||
|
|
||||||
|
def is_incomplete_option(all_args, cmd_param):
|
||||||
|
"""
|
||||||
|
:param all_args: the full original list of args supplied
|
||||||
|
:param cmd_param: the current command paramter
|
||||||
|
:return: whether or not the last option declaration (i.e. starts "-" or "--") is incomplete and
|
||||||
|
corresponds to this cmd_param. In other words whether this cmd_param option can still accept
|
||||||
|
values
|
||||||
|
"""
|
||||||
|
if not isinstance(cmd_param, Option):
|
||||||
|
return False
|
||||||
|
if cmd_param.is_flag:
|
||||||
|
return False
|
||||||
|
last_option = None
|
||||||
|
for index, arg_str in enumerate(reversed([arg for arg in all_args if arg != WORDBREAK])):
|
||||||
|
if index + 1 > cmd_param.nargs:
|
||||||
|
break
|
||||||
|
if start_of_option(arg_str):
|
||||||
|
last_option = arg_str
|
||||||
|
|
||||||
|
return True if last_option and last_option in cmd_param.opts else False
|
||||||
|
|
||||||
|
|
||||||
|
def is_incomplete_argument(current_params, cmd_param):
|
||||||
|
"""
|
||||||
|
:param current_params: the current params and values for this argument as already entered
|
||||||
|
:param cmd_param: the current command parameter
|
||||||
|
:return: whether or not the last argument is incomplete and corresponds to this cmd_param. In
|
||||||
|
other words whether or not the this cmd_param argument can still accept values
|
||||||
|
"""
|
||||||
|
if not isinstance(cmd_param, Argument):
|
||||||
|
return False
|
||||||
|
current_param_values = current_params[cmd_param.name]
|
||||||
|
if current_param_values is None:
|
||||||
|
return True
|
||||||
|
if cmd_param.nargs == -1:
|
||||||
|
return True
|
||||||
|
if isinstance(current_param_values, abc.Iterable) \
|
||||||
|
and cmd_param.nargs > 1 and len(current_param_values) < cmd_param.nargs:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def get_user_autocompletions(ctx, args, incomplete, cmd_param):
|
||||||
|
"""
|
||||||
|
:param ctx: context associated with the parsed command
|
||||||
|
:param args: full list of args
|
||||||
|
:param incomplete: the incomplete text to autocomplete
|
||||||
|
:param cmd_param: command definition
|
||||||
|
:return: all the possible user-specified completions for the param
|
||||||
|
"""
|
||||||
|
results = []
|
||||||
|
if isinstance(cmd_param.type, Choice):
|
||||||
|
# Choices don't support descriptions.
|
||||||
|
results = [(c, None)
|
||||||
|
for c in cmd_param.type.choices if str(c).startswith(incomplete)]
|
||||||
|
elif cmd_param.autocompletion is not None:
|
||||||
|
dynamic_completions = cmd_param.autocompletion(ctx=ctx,
|
||||||
|
args=args,
|
||||||
|
incomplete=incomplete)
|
||||||
|
results = [c if isinstance(c, tuple) else (c, None)
|
||||||
|
for c in dynamic_completions]
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
def get_visible_commands_starting_with(ctx, starts_with):
|
||||||
|
"""
|
||||||
|
:param ctx: context associated with the parsed command
|
||||||
|
:starts_with: string that visible commands must start with.
|
||||||
|
:return: all visible (not hidden) commands that start with starts_with.
|
||||||
|
"""
|
||||||
|
for c in ctx.command.list_commands(ctx):
|
||||||
|
if c.startswith(starts_with):
|
||||||
|
command = ctx.command.get_command(ctx, c)
|
||||||
|
if not command.hidden:
|
||||||
|
yield command
|
||||||
|
|
||||||
|
|
||||||
|
def add_subcommand_completions(ctx, incomplete, completions_out):
|
||||||
|
# Add subcommand completions.
|
||||||
|
if isinstance(ctx.command, MultiCommand):
|
||||||
|
completions_out.extend(
|
||||||
|
[(c.name, c.get_short_help_str()) for c in get_visible_commands_starting_with(ctx, incomplete)])
|
||||||
|
|
||||||
|
# Walk up the context list and add any other completion possibilities from chained commands
|
||||||
|
while ctx.parent is not None:
|
||||||
|
ctx = ctx.parent
|
||||||
|
if isinstance(ctx.command, MultiCommand) and ctx.command.chain:
|
||||||
|
remaining_commands = [c for c in get_visible_commands_starting_with(ctx, incomplete)
|
||||||
|
if c.name not in ctx.protected_args]
|
||||||
|
completions_out.extend([(c.name, c.get_short_help_str()) for c in remaining_commands])
|
||||||
|
|
||||||
|
|
||||||
|
def get_choices(cli, prog_name, args, incomplete):
|
||||||
|
"""
|
||||||
|
:param cli: command definition
|
||||||
|
:param prog_name: the program that is running
|
||||||
|
:param args: full list of args
|
||||||
|
:param incomplete: the incomplete text to autocomplete
|
||||||
|
:return: all the possible completions for the incomplete
|
||||||
|
"""
|
||||||
|
all_args = copy.deepcopy(args)
|
||||||
|
|
||||||
|
ctx = resolve_ctx(cli, prog_name, args)
|
||||||
|
if ctx is None:
|
||||||
|
return []
|
||||||
|
|
||||||
|
# In newer versions of bash long opts with '='s are partitioned, but it's easier to parse
|
||||||
|
# without the '='
|
||||||
|
if start_of_option(incomplete) and WORDBREAK in incomplete:
|
||||||
|
partition_incomplete = incomplete.partition(WORDBREAK)
|
||||||
|
all_args.append(partition_incomplete[0])
|
||||||
|
incomplete = partition_incomplete[2]
|
||||||
|
elif incomplete == WORDBREAK:
|
||||||
|
incomplete = ''
|
||||||
|
|
||||||
|
completions = []
|
||||||
|
if start_of_option(incomplete):
|
||||||
|
# completions for partial options
|
||||||
|
for param in ctx.command.params:
|
||||||
|
if isinstance(param, Option) and not param.hidden:
|
||||||
|
param_opts = [param_opt for param_opt in param.opts +
|
||||||
|
param.secondary_opts if param_opt not in all_args or param.multiple]
|
||||||
|
completions.extend([(o, param.help) for o in param_opts if o.startswith(incomplete)])
|
||||||
|
return completions
|
||||||
|
# completion for option values from user supplied values
|
||||||
|
for param in ctx.command.params:
|
||||||
|
if is_incomplete_option(all_args, param):
|
||||||
|
return get_user_autocompletions(ctx, all_args, incomplete, param)
|
||||||
|
# completion for argument values from user supplied values
|
||||||
|
for param in ctx.command.params:
|
||||||
|
if is_incomplete_argument(ctx.params, param):
|
||||||
|
return get_user_autocompletions(ctx, all_args, incomplete, param)
|
||||||
|
|
||||||
|
add_subcommand_completions(ctx, incomplete, completions)
|
||||||
|
# Sort before returning so that proper ordering can be enforced in custom types.
|
||||||
|
return sorted(completions)
|
||||||
|
|
||||||
|
|
||||||
|
def do_complete(cli, prog_name, include_descriptions):
|
||||||
|
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 = ''
|
||||||
|
|
||||||
|
for item in get_choices(cli, prog_name, args, incomplete):
|
||||||
|
echo(item[0])
|
||||||
|
if include_descriptions:
|
||||||
|
# ZSH has trouble dealing with empty array parameters when returned from commands, so use a well defined character '_' to indicate no description is present.
|
||||||
|
echo(item[1] if item[1] else '_')
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def bashcomplete(cli, prog_name, complete_var, complete_instr):
|
||||||
|
if complete_instr.startswith('source'):
|
||||||
|
shell = 'zsh' if complete_instr == 'source_zsh' else 'bash'
|
||||||
|
echo(get_completion_script(prog_name, complete_var, shell))
|
||||||
|
return True
|
||||||
|
elif complete_instr == 'complete' or complete_instr == 'complete_zsh':
|
||||||
|
return do_complete(cli, prog_name, complete_instr == 'complete_zsh')
|
||||||
|
return False
|
||||||
703
venv/lib/python3.6/site-packages/click/_compat.py
Normal file
703
venv/lib/python3.6/site-packages/click/_compat.py
Normal file
@ -0,0 +1,703 @@
|
|||||||
|
import re
|
||||||
|
import io
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import codecs
|
||||||
|
from weakref import WeakKeyDictionary
|
||||||
|
|
||||||
|
|
||||||
|
PY2 = sys.version_info[0] == 2
|
||||||
|
CYGWIN = sys.platform.startswith('cygwin')
|
||||||
|
# Determine local App Engine environment, per Google's own suggestion
|
||||||
|
APP_ENGINE = ('APPENGINE_RUNTIME' in os.environ and
|
||||||
|
'Development/' in os.environ['SERVER_SOFTWARE'])
|
||||||
|
WIN = sys.platform.startswith('win') and not APP_ENGINE
|
||||||
|
DEFAULT_COLUMNS = 80
|
||||||
|
|
||||||
|
|
||||||
|
_ansi_re = re.compile(r'\033\[((?:\d|;)*)([a-zA-Z])')
|
||||||
|
|
||||||
|
|
||||||
|
def get_filesystem_encoding():
|
||||||
|
return sys.getfilesystemencoding() or sys.getdefaultencoding()
|
||||||
|
|
||||||
|
|
||||||
|
def _make_text_stream(stream, encoding, errors,
|
||||||
|
force_readable=False, force_writable=False):
|
||||||
|
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):
|
||||||
|
"""Checks if a given encoding is ascii."""
|
||||||
|
try:
|
||||||
|
return codecs.lookup(encoding).name == 'ascii'
|
||||||
|
except LookupError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def get_best_encoding(stream):
|
||||||
|
"""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, encoding, errors,
|
||||||
|
force_readable=False, force_writable=False, **extra):
|
||||||
|
self._stream = stream = _FixupStream(stream, force_readable,
|
||||||
|
force_writable)
|
||||||
|
io.TextIOWrapper.__init__(self, stream, encoding, errors, **extra)
|
||||||
|
|
||||||
|
# The io module is a place where the Python 3 text behavior
|
||||||
|
# was forced upon Python 2, so we need to unbreak
|
||||||
|
# it to look like Python 2.
|
||||||
|
if PY2:
|
||||||
|
def write(self, x):
|
||||||
|
if isinstance(x, str) or is_bytes(x):
|
||||||
|
try:
|
||||||
|
self.flush()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return self.buffer.write(str(x))
|
||||||
|
return io.TextIOWrapper.write(self, x)
|
||||||
|
|
||||||
|
def writelines(self, lines):
|
||||||
|
for line in lines:
|
||||||
|
self.write(line)
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
try:
|
||||||
|
self.detach()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def isatty(self):
|
||||||
|
# https://bitbucket.org/pypy/pypy/issue/1803
|
||||||
|
return self._stream.isatty()
|
||||||
|
|
||||||
|
|
||||||
|
class _FixupStream(object):
|
||||||
|
"""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, force_readable=False, force_writable=False):
|
||||||
|
self._stream = stream
|
||||||
|
self._force_readable = force_readable
|
||||||
|
self._force_writable = force_writable
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
return getattr(self._stream, name)
|
||||||
|
|
||||||
|
def read1(self, size):
|
||||||
|
f = getattr(self._stream, 'read1', None)
|
||||||
|
if f is not None:
|
||||||
|
return f(size)
|
||||||
|
# We only dispatch to readline instead of read in Python 2 as we
|
||||||
|
# do not want cause problems with the different implementation
|
||||||
|
# of line buffering.
|
||||||
|
if PY2:
|
||||||
|
return self._stream.readline(size)
|
||||||
|
return self._stream.read(size)
|
||||||
|
|
||||||
|
def readable(self):
|
||||||
|
if self._force_readable:
|
||||||
|
return True
|
||||||
|
x = getattr(self._stream, 'readable', None)
|
||||||
|
if x is not None:
|
||||||
|
return x()
|
||||||
|
try:
|
||||||
|
self._stream.read(0)
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def writable(self):
|
||||||
|
if self._force_writable:
|
||||||
|
return True
|
||||||
|
x = getattr(self._stream, 'writable', None)
|
||||||
|
if x is not None:
|
||||||
|
return x()
|
||||||
|
try:
|
||||||
|
self._stream.write('')
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
self._stream.write(b'')
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def seekable(self):
|
||||||
|
x = getattr(self._stream, 'seekable', None)
|
||||||
|
if x is not None:
|
||||||
|
return x()
|
||||||
|
try:
|
||||||
|
self._stream.seek(self._stream.tell())
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
if PY2:
|
||||||
|
text_type = unicode
|
||||||
|
bytes = str
|
||||||
|
raw_input = raw_input
|
||||||
|
string_types = (str, unicode)
|
||||||
|
int_types = (int, long)
|
||||||
|
iteritems = lambda x: x.iteritems()
|
||||||
|
range_type = xrange
|
||||||
|
|
||||||
|
def is_bytes(x):
|
||||||
|
return isinstance(x, (buffer, bytearray))
|
||||||
|
|
||||||
|
_identifier_re = re.compile(r'^[a-zA-Z_][a-zA-Z0-9_]*$')
|
||||||
|
|
||||||
|
# For Windows, we need to force stdout/stdin/stderr to binary if it's
|
||||||
|
# fetched for that. This obviously is not the most correct way to do
|
||||||
|
# it as it changes global state. Unfortunately, there does not seem to
|
||||||
|
# be a clear better way to do it as just reopening the file in binary
|
||||||
|
# mode does not change anything.
|
||||||
|
#
|
||||||
|
# An option would be to do what Python 3 does and to open the file as
|
||||||
|
# binary only, patch it back to the system, and then use a wrapper
|
||||||
|
# stream that converts newlines. It's not quite clear what's the
|
||||||
|
# correct option here.
|
||||||
|
#
|
||||||
|
# This code also lives in _winconsole for the fallback to the console
|
||||||
|
# emulation stream.
|
||||||
|
#
|
||||||
|
# There are also Windows environments where the `msvcrt` module is not
|
||||||
|
# available (which is why we use try-catch instead of the WIN variable
|
||||||
|
# here), such as the Google App Engine development server on Windows. In
|
||||||
|
# those cases there is just nothing we can do.
|
||||||
|
def set_binary_mode(f):
|
||||||
|
return f
|
||||||
|
|
||||||
|
try:
|
||||||
|
import msvcrt
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
def set_binary_mode(f):
|
||||||
|
try:
|
||||||
|
fileno = f.fileno()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
msvcrt.setmode(fileno, os.O_BINARY)
|
||||||
|
return f
|
||||||
|
|
||||||
|
try:
|
||||||
|
import fcntl
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
def set_binary_mode(f):
|
||||||
|
try:
|
||||||
|
fileno = f.fileno()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
flags = fcntl.fcntl(fileno, fcntl.F_GETFL)
|
||||||
|
fcntl.fcntl(fileno, fcntl.F_SETFL, flags & ~os.O_NONBLOCK)
|
||||||
|
return f
|
||||||
|
|
||||||
|
def isidentifier(x):
|
||||||
|
return _identifier_re.search(x) is not None
|
||||||
|
|
||||||
|
def get_binary_stdin():
|
||||||
|
return set_binary_mode(sys.stdin)
|
||||||
|
|
||||||
|
def get_binary_stdout():
|
||||||
|
_wrap_std_stream('stdout')
|
||||||
|
return set_binary_mode(sys.stdout)
|
||||||
|
|
||||||
|
def get_binary_stderr():
|
||||||
|
_wrap_std_stream('stderr')
|
||||||
|
return set_binary_mode(sys.stderr)
|
||||||
|
|
||||||
|
def get_text_stdin(encoding=None, errors=None):
|
||||||
|
rv = _get_windows_console_stream(sys.stdin, encoding, errors)
|
||||||
|
if rv is not None:
|
||||||
|
return rv
|
||||||
|
return _make_text_stream(sys.stdin, encoding, errors,
|
||||||
|
force_readable=True)
|
||||||
|
|
||||||
|
def get_text_stdout(encoding=None, errors=None):
|
||||||
|
_wrap_std_stream('stdout')
|
||||||
|
rv = _get_windows_console_stream(sys.stdout, encoding, errors)
|
||||||
|
if rv is not None:
|
||||||
|
return rv
|
||||||
|
return _make_text_stream(sys.stdout, encoding, errors,
|
||||||
|
force_writable=True)
|
||||||
|
|
||||||
|
def get_text_stderr(encoding=None, errors=None):
|
||||||
|
_wrap_std_stream('stderr')
|
||||||
|
rv = _get_windows_console_stream(sys.stderr, encoding, errors)
|
||||||
|
if rv is not None:
|
||||||
|
return rv
|
||||||
|
return _make_text_stream(sys.stderr, encoding, errors,
|
||||||
|
force_writable=True)
|
||||||
|
|
||||||
|
def filename_to_ui(value):
|
||||||
|
if isinstance(value, bytes):
|
||||||
|
value = value.decode(get_filesystem_encoding(), 'replace')
|
||||||
|
return value
|
||||||
|
else:
|
||||||
|
import io
|
||||||
|
text_type = str
|
||||||
|
raw_input = input
|
||||||
|
string_types = (str,)
|
||||||
|
int_types = (int,)
|
||||||
|
range_type = range
|
||||||
|
isidentifier = lambda x: x.isidentifier()
|
||||||
|
iteritems = lambda x: iter(x.items())
|
||||||
|
|
||||||
|
def is_bytes(x):
|
||||||
|
return isinstance(x, (bytes, memoryview, bytearray))
|
||||||
|
|
||||||
|
def _is_binary_reader(stream, default=False):
|
||||||
|
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, default=False):
|
||||||
|
try:
|
||||||
|
stream.write(b'')
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
stream.write('')
|
||||||
|
return False
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return default
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _find_binary_reader(stream):
|
||||||
|
# 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 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 buf
|
||||||
|
|
||||||
|
def _find_binary_writer(stream):
|
||||||
|
# We need to figure out if the given stream is already binary.
|
||||||
|
# This can happen because the official docs recommend detatching
|
||||||
|
# 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 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 buf
|
||||||
|
|
||||||
|
def _stream_is_misconfigured(stream):
|
||||||
|
"""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_compatible_text_stream(stream, encoding, errors):
|
||||||
|
stream_encoding = getattr(stream, 'encoding', None)
|
||||||
|
stream_errors = getattr(stream, 'errors', None)
|
||||||
|
|
||||||
|
# Perfect match.
|
||||||
|
if stream_encoding == encoding and stream_errors == errors:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Otherwise, it's only a compatible stream if we did not ask for
|
||||||
|
# an encoding.
|
||||||
|
if encoding is None:
|
||||||
|
return stream_encoding is not None
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _force_correct_text_reader(text_reader, encoding, errors,
|
||||||
|
force_readable=False):
|
||||||
|
if _is_binary_reader(text_reader, False):
|
||||||
|
binary_reader = text_reader
|
||||||
|
else:
|
||||||
|
# If there is no target encoding set, we need to verify that the
|
||||||
|
# reader is not actually misconfigured.
|
||||||
|
if encoding is None and not _stream_is_misconfigured(text_reader):
|
||||||
|
return text_reader
|
||||||
|
|
||||||
|
if _is_compatible_text_stream(text_reader, encoding, errors):
|
||||||
|
return text_reader
|
||||||
|
|
||||||
|
# If the reader has no encoding, we try to find the underlying
|
||||||
|
# binary reader for it. If that fails because the environment is
|
||||||
|
# misconfigured, we silently go with the same reader because this
|
||||||
|
# is too common to happen. In that case, mojibake is better than
|
||||||
|
# exceptions.
|
||||||
|
binary_reader = _find_binary_reader(text_reader)
|
||||||
|
if binary_reader is None:
|
||||||
|
return text_reader
|
||||||
|
|
||||||
|
# At this point, we default the errors to replace instead of strict
|
||||||
|
# because nobody handles those errors anyways and at this point
|
||||||
|
# we're so fundamentally fucked that nothing can repair it.
|
||||||
|
if errors is None:
|
||||||
|
errors = 'replace'
|
||||||
|
return _make_text_stream(binary_reader, encoding, errors,
|
||||||
|
force_readable=force_readable)
|
||||||
|
|
||||||
|
def _force_correct_text_writer(text_writer, encoding, errors,
|
||||||
|
force_writable=False):
|
||||||
|
if _is_binary_writer(text_writer, False):
|
||||||
|
binary_writer = text_writer
|
||||||
|
else:
|
||||||
|
# If there is no target encoding set, we need to verify that the
|
||||||
|
# writer is not actually misconfigured.
|
||||||
|
if encoding is None and not _stream_is_misconfigured(text_writer):
|
||||||
|
return text_writer
|
||||||
|
|
||||||
|
if _is_compatible_text_stream(text_writer, encoding, errors):
|
||||||
|
return text_writer
|
||||||
|
|
||||||
|
# If the writer has no encoding, we try to find the underlying
|
||||||
|
# binary writer for it. If that fails because the environment is
|
||||||
|
# misconfigured, we silently go with the same writer because this
|
||||||
|
# is too common to happen. In that case, mojibake is better than
|
||||||
|
# exceptions.
|
||||||
|
binary_writer = _find_binary_writer(text_writer)
|
||||||
|
if binary_writer is None:
|
||||||
|
return text_writer
|
||||||
|
|
||||||
|
# At this point, we default the errors to replace instead of strict
|
||||||
|
# because nobody handles those errors anyways and at this point
|
||||||
|
# we're so fundamentally fucked that nothing can repair it.
|
||||||
|
if errors is None:
|
||||||
|
errors = 'replace'
|
||||||
|
return _make_text_stream(binary_writer, encoding, errors,
|
||||||
|
force_writable=force_writable)
|
||||||
|
|
||||||
|
def get_binary_stdin():
|
||||||
|
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():
|
||||||
|
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():
|
||||||
|
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=None, errors=None):
|
||||||
|
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=None, errors=None):
|
||||||
|
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=None, errors=None):
|
||||||
|
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 filename_to_ui(value):
|
||||||
|
if isinstance(value, bytes):
|
||||||
|
value = value.decode(get_filesystem_encoding(), 'replace')
|
||||||
|
else:
|
||||||
|
value = value.encode('utf-8', 'surrogateescape') \
|
||||||
|
.decode('utf-8', 'replace')
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def get_streerror(e, default=None):
|
||||||
|
if hasattr(e, 'strerror'):
|
||||||
|
msg = e.strerror
|
||||||
|
else:
|
||||||
|
if default is not None:
|
||||||
|
msg = default
|
||||||
|
else:
|
||||||
|
msg = str(e)
|
||||||
|
if isinstance(msg, bytes):
|
||||||
|
msg = msg.decode('utf-8', 'replace')
|
||||||
|
return msg
|
||||||
|
|
||||||
|
|
||||||
|
def open_stream(filename, mode='r', encoding=None, errors='strict',
|
||||||
|
atomic=False):
|
||||||
|
# Standard streams first. These are simple because they don't need
|
||||||
|
# special handling for the atomic flag. It's entirely ignored.
|
||||||
|
if filename == '-':
|
||||||
|
if any(m in mode for m in ['w', 'a', 'x']):
|
||||||
|
if 'b' in mode:
|
||||||
|
return get_binary_stdout(), False
|
||||||
|
return get_text_stdout(encoding=encoding, errors=errors), False
|
||||||
|
if 'b' in mode:
|
||||||
|
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:
|
||||||
|
if encoding is None:
|
||||||
|
return open(filename, mode), True
|
||||||
|
return io.open(filename, mode, encoding=encoding, errors=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 tempfile
|
||||||
|
fd, tmp_filename = tempfile.mkstemp(dir=os.path.dirname(filename),
|
||||||
|
prefix='.__atomic-write')
|
||||||
|
|
||||||
|
if encoding is not None:
|
||||||
|
f = io.open(fd, mode, encoding=encoding, errors=errors)
|
||||||
|
else:
|
||||||
|
f = os.fdopen(fd, mode)
|
||||||
|
|
||||||
|
return _AtomicFile(f, tmp_filename, os.path.realpath(filename)), True
|
||||||
|
|
||||||
|
|
||||||
|
# Used in a destructor call, needs extra protection from interpreter cleanup.
|
||||||
|
if hasattr(os, 'replace'):
|
||||||
|
_replace = os.replace
|
||||||
|
_can_replace = True
|
||||||
|
else:
|
||||||
|
_replace = os.rename
|
||||||
|
_can_replace = not WIN
|
||||||
|
|
||||||
|
|
||||||
|
class _AtomicFile(object):
|
||||||
|
|
||||||
|
def __init__(self, f, tmp_filename, real_filename):
|
||||||
|
self._f = f
|
||||||
|
self._tmp_filename = tmp_filename
|
||||||
|
self._real_filename = real_filename
|
||||||
|
self.closed = False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return self._real_filename
|
||||||
|
|
||||||
|
def close(self, delete=False):
|
||||||
|
if self.closed:
|
||||||
|
return
|
||||||
|
self._f.close()
|
||||||
|
if not _can_replace:
|
||||||
|
try:
|
||||||
|
os.remove(self._real_filename)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
_replace(self._tmp_filename, self._real_filename)
|
||||||
|
self.closed = True
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
return getattr(self._f, name)
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_value, tb):
|
||||||
|
self.close(delete=exc_type is not None)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return repr(self._f)
|
||||||
|
|
||||||
|
|
||||||
|
auto_wrap_for_ansi = None
|
||||||
|
colorama = None
|
||||||
|
get_winterm_size = None
|
||||||
|
|
||||||
|
|
||||||
|
def strip_ansi(value):
|
||||||
|
return _ansi_re.sub('', value)
|
||||||
|
|
||||||
|
|
||||||
|
def should_strip_ansi(stream=None, color=None):
|
||||||
|
if color is None:
|
||||||
|
if stream is None:
|
||||||
|
stream = sys.stdin
|
||||||
|
return not isatty(stream)
|
||||||
|
return not color
|
||||||
|
|
||||||
|
|
||||||
|
# If we're on Windows, we provide transparent integration through
|
||||||
|
# colorama. This will make ANSI colors through the echo function
|
||||||
|
# work automatically.
|
||||||
|
if WIN:
|
||||||
|
# Windows has a smaller terminal
|
||||||
|
DEFAULT_COLUMNS = 79
|
||||||
|
|
||||||
|
from ._winconsole import _get_windows_console_stream, _wrap_std_stream
|
||||||
|
|
||||||
|
def _get_argv_encoding():
|
||||||
|
import locale
|
||||||
|
return locale.getpreferredencoding()
|
||||||
|
|
||||||
|
if PY2:
|
||||||
|
def raw_input(prompt=''):
|
||||||
|
sys.stderr.flush()
|
||||||
|
if prompt:
|
||||||
|
stdout = _default_text_stdout()
|
||||||
|
stdout.write(prompt)
|
||||||
|
stdin = _default_text_stdin()
|
||||||
|
return stdin.readline().rstrip('\r\n')
|
||||||
|
|
||||||
|
try:
|
||||||
|
import colorama
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
_ansi_stream_wrappers = WeakKeyDictionary()
|
||||||
|
|
||||||
|
def auto_wrap_for_ansi(stream, color=None):
|
||||||
|
"""This function wraps a stream so that calls through colorama
|
||||||
|
are issued to the win32 console API to recolor on demand. It
|
||||||
|
also ensures to reset the colors if a write call is interrupted
|
||||||
|
to not destroy the console afterwards.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
cached = _ansi_stream_wrappers.get(stream)
|
||||||
|
except Exception:
|
||||||
|
cached = None
|
||||||
|
if cached is not None:
|
||||||
|
return cached
|
||||||
|
strip = should_strip_ansi(stream, color)
|
||||||
|
ansi_wrapper = colorama.AnsiToWin32(stream, strip=strip)
|
||||||
|
rv = ansi_wrapper.stream
|
||||||
|
_write = rv.write
|
||||||
|
|
||||||
|
def _safe_write(s):
|
||||||
|
try:
|
||||||
|
return _write(s)
|
||||||
|
except:
|
||||||
|
ansi_wrapper.reset_all()
|
||||||
|
raise
|
||||||
|
|
||||||
|
rv.write = _safe_write
|
||||||
|
try:
|
||||||
|
_ansi_stream_wrappers[stream] = rv
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return rv
|
||||||
|
|
||||||
|
def get_winterm_size():
|
||||||
|
win = colorama.win32.GetConsoleScreenBufferInfo(
|
||||||
|
colorama.win32.STDOUT).srWindow
|
||||||
|
return win.Right - win.Left, win.Bottom - win.Top
|
||||||
|
else:
|
||||||
|
def _get_argv_encoding():
|
||||||
|
return getattr(sys.stdin, 'encoding', None) or get_filesystem_encoding()
|
||||||
|
|
||||||
|
_get_windows_console_stream = lambda *x: None
|
||||||
|
_wrap_std_stream = lambda *x: None
|
||||||
|
|
||||||
|
|
||||||
|
def term_len(x):
|
||||||
|
return len(strip_ansi(x))
|
||||||
|
|
||||||
|
|
||||||
|
def isatty(stream):
|
||||||
|
try:
|
||||||
|
return stream.isatty()
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _make_cached_stream_func(src_func, wrapper_func):
|
||||||
|
cache = WeakKeyDictionary()
|
||||||
|
def func():
|
||||||
|
stream = src_func()
|
||||||
|
try:
|
||||||
|
rv = cache.get(stream)
|
||||||
|
except Exception:
|
||||||
|
rv = None
|
||||||
|
if rv is not None:
|
||||||
|
return rv
|
||||||
|
rv = wrapper_func()
|
||||||
|
try:
|
||||||
|
stream = src_func() # In case wrapper_func() modified the stream
|
||||||
|
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 = {
|
||||||
|
'stdin': get_binary_stdin,
|
||||||
|
'stdout': get_binary_stdout,
|
||||||
|
'stderr': get_binary_stderr,
|
||||||
|
}
|
||||||
|
|
||||||
|
text_streams = {
|
||||||
|
'stdin': get_text_stdin,
|
||||||
|
'stdout': get_text_stdout,
|
||||||
|
'stderr': get_text_stderr,
|
||||||
|
}
|
||||||
621
venv/lib/python3.6/site-packages/click/_termui_impl.py
Normal file
621
venv/lib/python3.6/site-packages/click/_termui_impl.py
Normal file
@ -0,0 +1,621 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
click._termui_impl
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
:copyright: © 2014 by the Pallets team.
|
||||||
|
:license: BSD, see LICENSE.rst for more details.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import math
|
||||||
|
import contextlib
|
||||||
|
from ._compat import _default_text_stdout, range_type, PY2, isatty, \
|
||||||
|
open_stream, strip_ansi, term_len, get_best_encoding, WIN, int_types, \
|
||||||
|
CYGWIN
|
||||||
|
from .utils import echo
|
||||||
|
from .exceptions import ClickException
|
||||||
|
|
||||||
|
|
||||||
|
if os.name == 'nt':
|
||||||
|
BEFORE_BAR = '\r'
|
||||||
|
AFTER_BAR = '\n'
|
||||||
|
else:
|
||||||
|
BEFORE_BAR = '\r\033[?25l'
|
||||||
|
AFTER_BAR = '\033[?25h\n'
|
||||||
|
|
||||||
|
|
||||||
|
def _length_hint(obj):
|
||||||
|
"""Returns the length hint of an object."""
|
||||||
|
try:
|
||||||
|
return len(obj)
|
||||||
|
except (AttributeError, TypeError):
|
||||||
|
try:
|
||||||
|
get_hint = type(obj).__length_hint__
|
||||||
|
except AttributeError:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
hint = get_hint(obj)
|
||||||
|
except TypeError:
|
||||||
|
return None
|
||||||
|
if hint is NotImplemented or \
|
||||||
|
not isinstance(hint, int_types) or \
|
||||||
|
hint < 0:
|
||||||
|
return None
|
||||||
|
return hint
|
||||||
|
|
||||||
|
|
||||||
|
class ProgressBar(object):
|
||||||
|
|
||||||
|
def __init__(self, iterable, length=None, fill_char='#', empty_char=' ',
|
||||||
|
bar_template='%(bar)s', info_sep=' ', show_eta=True,
|
||||||
|
show_percent=None, show_pos=False, item_show_func=None,
|
||||||
|
label=None, file=None, color=None, width=30):
|
||||||
|
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.width = width
|
||||||
|
self.autowidth = width == 0
|
||||||
|
|
||||||
|
if length is None:
|
||||||
|
length = _length_hint(iterable)
|
||||||
|
if iterable is None:
|
||||||
|
if length is None:
|
||||||
|
raise TypeError('iterable or length is required')
|
||||||
|
iterable = range_type(length)
|
||||||
|
self.iter = iter(iterable)
|
||||||
|
self.length = length
|
||||||
|
self.length_known = length is not None
|
||||||
|
self.pos = 0
|
||||||
|
self.avg = []
|
||||||
|
self.start = self.last_eta = time.time()
|
||||||
|
self.eta_known = False
|
||||||
|
self.finished = False
|
||||||
|
self.max_width = None
|
||||||
|
self.entered = False
|
||||||
|
self.current_item = None
|
||||||
|
self.is_hidden = not isatty(self.file)
|
||||||
|
self._last_line = None
|
||||||
|
self.short_limit = 0.5
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
self.entered = True
|
||||||
|
self.render_progress()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_value, tb):
|
||||||
|
self.render_finish()
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
if not self.entered:
|
||||||
|
raise RuntimeError('You need to use progress bars in a with block.')
|
||||||
|
self.render_progress()
|
||||||
|
return self.generator()
|
||||||
|
|
||||||
|
def is_fast(self):
|
||||||
|
return time.time() - self.start <= self.short_limit
|
||||||
|
|
||||||
|
def render_finish(self):
|
||||||
|
if self.is_hidden or self.is_fast():
|
||||||
|
return
|
||||||
|
self.file.write(AFTER_BAR)
|
||||||
|
self.file.flush()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pct(self):
|
||||||
|
if self.finished:
|
||||||
|
return 1.0
|
||||||
|
return min(self.pos / (float(self.length) or 1), 1.0)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def time_per_iteration(self):
|
||||||
|
if not self.avg:
|
||||||
|
return 0.0
|
||||||
|
return sum(self.avg) / float(len(self.avg))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def eta(self):
|
||||||
|
if self.length_known and not self.finished:
|
||||||
|
return self.time_per_iteration * (self.length - self.pos)
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
def format_eta(self):
|
||||||
|
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:
|
||||||
|
days = t
|
||||||
|
return '%dd %02d:%02d:%02d' % (days, hours, minutes, seconds)
|
||||||
|
else:
|
||||||
|
return '%02d:%02d:%02d' % (hours, minutes, seconds)
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def format_pos(self):
|
||||||
|
pos = str(self.pos)
|
||||||
|
if self.length_known:
|
||||||
|
pos += '/%s' % self.length
|
||||||
|
return pos
|
||||||
|
|
||||||
|
def format_pct(self):
|
||||||
|
return ('% 4d%%' % int(self.pct * 100))[1:]
|
||||||
|
|
||||||
|
def format_bar(self):
|
||||||
|
if self.length_known:
|
||||||
|
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:
|
||||||
|
bar = list(self.empty_char * (self.width or 1))
|
||||||
|
if self.time_per_iteration != 0:
|
||||||
|
bar[int((math.cos(self.pos * self.time_per_iteration)
|
||||||
|
/ 2.0 + 0.5) * self.width)] = self.fill_char
|
||||||
|
bar = ''.join(bar)
|
||||||
|
return bar
|
||||||
|
|
||||||
|
def format_progress_line(self):
|
||||||
|
show_percent = self.show_percent
|
||||||
|
|
||||||
|
info_bits = []
|
||||||
|
if self.length_known 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):
|
||||||
|
from .termui import get_terminal_size
|
||||||
|
|
||||||
|
if self.is_hidden:
|
||||||
|
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, get_terminal_size()[0] - clutter_length)
|
||||||
|
if new_width < old_width:
|
||||||
|
buf.append(BEFORE_BAR)
|
||||||
|
buf.append(' ' * self.max_width)
|
||||||
|
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 and not self.is_fast():
|
||||||
|
self._last_line = line
|
||||||
|
echo(line, file=self.file, color=self.color, nl=False)
|
||||||
|
self.file.flush()
|
||||||
|
|
||||||
|
def make_step(self, n_steps):
|
||||||
|
self.pos += n_steps
|
||||||
|
if self.length_known 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_known
|
||||||
|
|
||||||
|
def update(self, n_steps):
|
||||||
|
self.make_step(n_steps)
|
||||||
|
self.render_progress()
|
||||||
|
|
||||||
|
def finish(self):
|
||||||
|
self.eta_known = 0
|
||||||
|
self.current_item = None
|
||||||
|
self.finished = True
|
||||||
|
|
||||||
|
def generator(self):
|
||||||
|
"""
|
||||||
|
Returns a generator which yields the items added to the bar during
|
||||||
|
construction, and updates the progress bar *after* the yielded block
|
||||||
|
returns.
|
||||||
|
"""
|
||||||
|
if not self.entered:
|
||||||
|
raise RuntimeError('You need to use progress bars in a with block.')
|
||||||
|
|
||||||
|
if self.is_hidden:
|
||||||
|
for rv in self.iter:
|
||||||
|
yield rv
|
||||||
|
else:
|
||||||
|
for rv in self.iter:
|
||||||
|
self.current_item = rv
|
||||||
|
yield rv
|
||||||
|
self.update(1)
|
||||||
|
self.finish()
|
||||||
|
self.render_progress()
|
||||||
|
|
||||||
|
|
||||||
|
def pager(generator, color=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('more "%s"' % filename) == 0:
|
||||||
|
return _pipepager(generator, 'more', color)
|
||||||
|
return _nullpager(stdout, generator, color)
|
||||||
|
finally:
|
||||||
|
os.unlink(filename)
|
||||||
|
|
||||||
|
|
||||||
|
def _pipepager(generator, cmd, color):
|
||||||
|
"""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 = 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)
|
||||||
|
encoding = get_best_encoding(c.stdin)
|
||||||
|
try:
|
||||||
|
for text in generator:
|
||||||
|
if not color:
|
||||||
|
text = strip_ansi(text)
|
||||||
|
|
||||||
|
c.stdin.write(text.encode(encoding, 'replace'))
|
||||||
|
except (IOError, KeyboardInterrupt):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
c.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, cmd, color):
|
||||||
|
"""Page through text by invoking a program on a temporary file."""
|
||||||
|
import tempfile
|
||||||
|
filename = tempfile.mktemp()
|
||||||
|
# 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(cmd + ' "' + filename + '"')
|
||||||
|
finally:
|
||||||
|
os.unlink(filename)
|
||||||
|
|
||||||
|
|
||||||
|
def _nullpager(stream, generator, color):
|
||||||
|
"""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(object):
|
||||||
|
|
||||||
|
def __init__(self, editor=None, env=None, require_save=True,
|
||||||
|
extension='.txt'):
|
||||||
|
self.editor = editor
|
||||||
|
self.env = env
|
||||||
|
self.require_save = require_save
|
||||||
|
self.extension = extension
|
||||||
|
|
||||||
|
def get_editor(self):
|
||||||
|
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 'vim', 'nano':
|
||||||
|
if os.system('which %s >/dev/null 2>&1' % editor) == 0:
|
||||||
|
return editor
|
||||||
|
return 'vi'
|
||||||
|
|
||||||
|
def edit_file(self, filename):
|
||||||
|
import subprocess
|
||||||
|
editor = self.get_editor()
|
||||||
|
if self.env:
|
||||||
|
environ = os.environ.copy()
|
||||||
|
environ.update(self.env)
|
||||||
|
else:
|
||||||
|
environ = None
|
||||||
|
try:
|
||||||
|
c = subprocess.Popen('%s "%s"' % (editor, filename),
|
||||||
|
env=environ, shell=True)
|
||||||
|
exit_code = c.wait()
|
||||||
|
if exit_code != 0:
|
||||||
|
raise ClickException('%s: Editing failed!' % editor)
|
||||||
|
except OSError as e:
|
||||||
|
raise ClickException('%s: Editing failed: %s' % (editor, e))
|
||||||
|
|
||||||
|
def edit(self, text):
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
text = text or ''
|
||||||
|
if text and not text.endswith('\n'):
|
||||||
|
text += '\n'
|
||||||
|
|
||||||
|
fd, name = tempfile.mkstemp(prefix='editor-', suffix=self.extension)
|
||||||
|
try:
|
||||||
|
if WIN:
|
||||||
|
encoding = 'utf-8-sig'
|
||||||
|
text = text.replace('\n', '\r\n')
|
||||||
|
else:
|
||||||
|
encoding = 'utf-8'
|
||||||
|
text = text.encode(encoding)
|
||||||
|
|
||||||
|
f = os.fdopen(fd, 'wb')
|
||||||
|
f.write(text)
|
||||||
|
f.close()
|
||||||
|
timestamp = os.path.getmtime(name)
|
||||||
|
|
||||||
|
self.edit_file(name)
|
||||||
|
|
||||||
|
if self.require_save \
|
||||||
|
and os.path.getmtime(name) == timestamp:
|
||||||
|
return None
|
||||||
|
|
||||||
|
f = open(name, 'rb')
|
||||||
|
try:
|
||||||
|
rv = f.read()
|
||||||
|
finally:
|
||||||
|
f.close()
|
||||||
|
return rv.decode('utf-8-sig').replace('\r\n', '\n')
|
||||||
|
finally:
|
||||||
|
os.unlink(name)
|
||||||
|
|
||||||
|
|
||||||
|
def open_url(url, wait=False, locate=False):
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
def _unquote_file(url):
|
||||||
|
try:
|
||||||
|
import urllib
|
||||||
|
except ImportError:
|
||||||
|
import urllib
|
||||||
|
if url.startswith('file://'):
|
||||||
|
url = urllib.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)
|
||||||
|
args = 'explorer /select,"%s"' % _unquote_file(
|
||||||
|
url.replace('"', ''))
|
||||||
|
else:
|
||||||
|
args = 'start %s "" "%s"' % (
|
||||||
|
wait and '/WAIT' or '', url.replace('"', ''))
|
||||||
|
return os.system(args)
|
||||||
|
elif CYGWIN:
|
||||||
|
if locate:
|
||||||
|
url = _unquote_file(url)
|
||||||
|
args = 'cygstart "%s"' % (os.path.dirname(url).replace('"', ''))
|
||||||
|
else:
|
||||||
|
args = 'cygstart %s "%s"' % (
|
||||||
|
wait and '-w' or '', url.replace('"', ''))
|
||||||
|
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):
|
||||||
|
if ch == u'\x03':
|
||||||
|
raise KeyboardInterrupt()
|
||||||
|
if ch == u'\x04' and not WIN: # Unix-like, Ctrl+D
|
||||||
|
raise EOFError()
|
||||||
|
if ch == u'\x1a' and WIN: # Windows, Ctrl+Z
|
||||||
|
raise EOFError()
|
||||||
|
|
||||||
|
|
||||||
|
if WIN:
|
||||||
|
import msvcrt
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def raw_terminal():
|
||||||
|
yield
|
||||||
|
|
||||||
|
def getchar(echo):
|
||||||
|
# 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`.
|
||||||
|
if echo:
|
||||||
|
func = msvcrt.getwche
|
||||||
|
else:
|
||||||
|
func = msvcrt.getwch
|
||||||
|
|
||||||
|
rv = func()
|
||||||
|
if rv in (u'\x00', u'\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():
|
||||||
|
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):
|
||||||
|
with raw_terminal() as fd:
|
||||||
|
ch = os.read(fd, 32)
|
||||||
|
ch = ch.decode(get_best_encoding(sys.stdin), 'replace')
|
||||||
|
if echo and isatty(sys.stdout):
|
||||||
|
sys.stdout.write(ch)
|
||||||
|
_translate_ch_to_exc(ch)
|
||||||
|
return ch
|
||||||
38
venv/lib/python3.6/site-packages/click/_textwrap.py
Normal file
38
venv/lib/python3.6/site-packages/click/_textwrap.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import textwrap
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
|
||||||
|
class TextWrapper(textwrap.TextWrapper):
|
||||||
|
|
||||||
|
def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
|
||||||
|
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):
|
||||||
|
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):
|
||||||
|
rv = []
|
||||||
|
for idx, line in enumerate(text.splitlines()):
|
||||||
|
indent = self.initial_indent
|
||||||
|
if idx > 0:
|
||||||
|
indent = self.subsequent_indent
|
||||||
|
rv.append(indent + line)
|
||||||
|
return '\n'.join(rv)
|
||||||
125
venv/lib/python3.6/site-packages/click/_unicodefun.py
Normal file
125
venv/lib/python3.6/site-packages/click/_unicodefun.py
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import codecs
|
||||||
|
|
||||||
|
from ._compat import PY2
|
||||||
|
|
||||||
|
|
||||||
|
# If someone wants to vendor click, we want to ensure the
|
||||||
|
# correct package is discovered. Ideally we could use a
|
||||||
|
# relative import here but unfortunately Python does not
|
||||||
|
# support that.
|
||||||
|
click = sys.modules[__name__.rsplit('.', 1)[0]]
|
||||||
|
|
||||||
|
|
||||||
|
def _find_unicode_literals_frame():
|
||||||
|
import __future__
|
||||||
|
if not hasattr(sys, '_getframe'): # not all Python implementations have it
|
||||||
|
return 0
|
||||||
|
frm = sys._getframe(1)
|
||||||
|
idx = 1
|
||||||
|
while frm is not None:
|
||||||
|
if frm.f_globals.get('__name__', '').startswith('click.'):
|
||||||
|
frm = frm.f_back
|
||||||
|
idx += 1
|
||||||
|
elif frm.f_code.co_flags & __future__.unicode_literals.compiler_flag:
|
||||||
|
return idx
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def _check_for_unicode_literals():
|
||||||
|
if not __debug__:
|
||||||
|
return
|
||||||
|
if not PY2 or click.disable_unicode_literals_warning:
|
||||||
|
return
|
||||||
|
bad_frame = _find_unicode_literals_frame()
|
||||||
|
if bad_frame <= 0:
|
||||||
|
return
|
||||||
|
from warnings import warn
|
||||||
|
warn(Warning('Click detected the use of the unicode_literals '
|
||||||
|
'__future__ import. This is heavily discouraged '
|
||||||
|
'because it can introduce subtle bugs in your '
|
||||||
|
'code. You should instead use explicit u"" literals '
|
||||||
|
'for your unicode strings. For more information see '
|
||||||
|
'https://click.palletsprojects.com/python3/'),
|
||||||
|
stacklevel=bad_frame)
|
||||||
|
|
||||||
|
|
||||||
|
def _verify_python3_env():
|
||||||
|
"""Ensures that the environment is good for unicode on Python 3."""
|
||||||
|
if PY2:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
import locale
|
||||||
|
fs_enc = codecs.lookup(locale.getpreferredencoding()).name
|
||||||
|
except Exception:
|
||||||
|
fs_enc = 'ascii'
|
||||||
|
if fs_enc != 'ascii':
|
||||||
|
return
|
||||||
|
|
||||||
|
extra = ''
|
||||||
|
if os.name == 'posix':
|
||||||
|
import subprocess
|
||||||
|
try:
|
||||||
|
rv = subprocess.Popen(['locale', '-a'], stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE).communicate()[0]
|
||||||
|
except OSError:
|
||||||
|
rv = b''
|
||||||
|
good_locales = set()
|
||||||
|
has_c_utf8 = False
|
||||||
|
|
||||||
|
# Make sure we're operating on text here.
|
||||||
|
if isinstance(rv, bytes):
|
||||||
|
rv = rv.decode('ascii', 'replace')
|
||||||
|
|
||||||
|
for line in rv.splitlines():
|
||||||
|
locale = line.strip()
|
||||||
|
if locale.lower().endswith(('.utf-8', '.utf8')):
|
||||||
|
good_locales.add(locale)
|
||||||
|
if locale.lower() in ('c.utf8', 'c.utf-8'):
|
||||||
|
has_c_utf8 = True
|
||||||
|
|
||||||
|
extra += '\n\n'
|
||||||
|
if not good_locales:
|
||||||
|
extra += (
|
||||||
|
'Additional information: on this system no suitable UTF-8\n'
|
||||||
|
'locales were discovered. This most likely requires resolving\n'
|
||||||
|
'by reconfiguring the locale system.'
|
||||||
|
)
|
||||||
|
elif has_c_utf8:
|
||||||
|
extra += (
|
||||||
|
'This system supports the C.UTF-8 locale which is recommended.\n'
|
||||||
|
'You might be able to resolve your issue by exporting the\n'
|
||||||
|
'following environment variables:\n\n'
|
||||||
|
' export LC_ALL=C.UTF-8\n'
|
||||||
|
' export LANG=C.UTF-8'
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
extra += (
|
||||||
|
'This system lists a couple of UTF-8 supporting locales that\n'
|
||||||
|
'you can pick from. The following suitable locales were\n'
|
||||||
|
'discovered: %s'
|
||||||
|
) % ', '.join(sorted(good_locales))
|
||||||
|
|
||||||
|
bad_locale = None
|
||||||
|
for locale in os.environ.get('LC_ALL'), os.environ.get('LANG'):
|
||||||
|
if locale and locale.lower().endswith(('.utf-8', '.utf8')):
|
||||||
|
bad_locale = locale
|
||||||
|
if locale is not None:
|
||||||
|
break
|
||||||
|
if bad_locale is not None:
|
||||||
|
extra += (
|
||||||
|
'\n\nClick discovered that you exported a UTF-8 locale\n'
|
||||||
|
'but the locale system could not pick up from it because\n'
|
||||||
|
'it does not exist. The exported locale is "%s" but it\n'
|
||||||
|
'is not supported'
|
||||||
|
) % bad_locale
|
||||||
|
|
||||||
|
raise RuntimeError(
|
||||||
|
'Click will abort further execution because Python 3 was'
|
||||||
|
' configured to use ASCII as encoding for the environment.'
|
||||||
|
' Consult https://click.palletsprojects.com/en/7.x/python3/ for'
|
||||||
|
' mitigation steps.' + extra
|
||||||
|
)
|
||||||
307
venv/lib/python3.6/site-packages/click/_winconsole.py
Normal file
307
venv/lib/python3.6/site-packages/click/_winconsole.py
Normal file
@ -0,0 +1,307 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# 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 prmopt.
|
||||||
|
|
||||||
|
import io
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import zlib
|
||||||
|
import time
|
||||||
|
import ctypes
|
||||||
|
import msvcrt
|
||||||
|
from ._compat import _NonClosingTextIOWrapper, text_type, PY2
|
||||||
|
from ctypes import byref, POINTER, c_int, c_char, c_char_p, \
|
||||||
|
c_void_p, py_object, c_ssize_t, c_ulong, windll, WINFUNCTYPE
|
||||||
|
try:
|
||||||
|
from ctypes import pythonapi
|
||||||
|
PyObject_GetBuffer = pythonapi.PyObject_GetBuffer
|
||||||
|
PyBuffer_Release = pythonapi.PyBuffer_Release
|
||||||
|
except ImportError:
|
||||||
|
pythonapi = None
|
||||||
|
from ctypes.wintypes import LPWSTR, LPCWSTR
|
||||||
|
|
||||||
|
|
||||||
|
c_ssize_p = POINTER(c_ssize_t)
|
||||||
|
|
||||||
|
kernel32 = windll.kernel32
|
||||||
|
GetStdHandle = kernel32.GetStdHandle
|
||||||
|
ReadConsoleW = kernel32.ReadConsoleW
|
||||||
|
WriteConsoleW = kernel32.WriteConsoleW
|
||||||
|
GetLastError = kernel32.GetLastError
|
||||||
|
GetCommandLineW = WINFUNCTYPE(LPWSTR)(
|
||||||
|
('GetCommandLineW', windll.kernel32))
|
||||||
|
CommandLineToArgvW = WINFUNCTYPE(
|
||||||
|
POINTER(LPWSTR), LPCWSTR, POINTER(c_int))(
|
||||||
|
('CommandLineToArgvW', windll.shell32))
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
class Py_buffer(ctypes.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)
|
||||||
|
]
|
||||||
|
|
||||||
|
if PY2:
|
||||||
|
_fields_.insert(-1, ('smalltable', c_ssize_t * 2))
|
||||||
|
|
||||||
|
|
||||||
|
# On PyPy we cannot get buffers so our ability to operate here is
|
||||||
|
# serverly limited.
|
||||||
|
if pythonapi is None:
|
||||||
|
get_buffer = None
|
||||||
|
else:
|
||||||
|
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):
|
||||||
|
io.RawIOBase.isatty(self)
|
||||||
|
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(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('Windows error: %s' % 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 'Windows error %s' % 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(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(object):
|
||||||
|
|
||||||
|
def __init__(self, text_stream, byte_stream):
|
||||||
|
self._text_stream = text_stream
|
||||||
|
self.buffer = byte_stream
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return self.buffer.name
|
||||||
|
|
||||||
|
def write(self, x):
|
||||||
|
if isinstance(x, text_type):
|
||||||
|
return self._text_stream.write(x)
|
||||||
|
try:
|
||||||
|
self.flush()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return self.buffer.write(x)
|
||||||
|
|
||||||
|
def writelines(self, lines):
|
||||||
|
for line in lines:
|
||||||
|
self.write(line)
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
return getattr(self._text_stream, name)
|
||||||
|
|
||||||
|
def isatty(self):
|
||||||
|
return self.buffer.isatty()
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<ConsoleStream name=%r encoding=%r>' % (
|
||||||
|
self.name,
|
||||||
|
self.encoding,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class WindowsChunkedWriter(object):
|
||||||
|
"""
|
||||||
|
Wraps a stream (such as stdout), acting as a transparent proxy for all
|
||||||
|
attribute access apart from method 'write()' which we wrap to write in
|
||||||
|
limited chunks due to a Windows limitation on binary console streams.
|
||||||
|
"""
|
||||||
|
def __init__(self, wrapped):
|
||||||
|
# double-underscore everything to prevent clashes with names of
|
||||||
|
# attributes on the wrapped stream object.
|
||||||
|
self.__wrapped = wrapped
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
return getattr(self.__wrapped, name)
|
||||||
|
|
||||||
|
def write(self, text):
|
||||||
|
total_to_write = len(text)
|
||||||
|
written = 0
|
||||||
|
|
||||||
|
while written < total_to_write:
|
||||||
|
to_write = min(total_to_write - written, MAX_BYTES_WRITTEN)
|
||||||
|
self.__wrapped.write(text[written:written+to_write])
|
||||||
|
written += to_write
|
||||||
|
|
||||||
|
|
||||||
|
_wrapped_std_streams = set()
|
||||||
|
|
||||||
|
|
||||||
|
def _wrap_std_stream(name):
|
||||||
|
# Python 2 & Windows 7 and below
|
||||||
|
if PY2 and sys.getwindowsversion()[:2] <= (6, 1) and name not in _wrapped_std_streams:
|
||||||
|
setattr(sys, name, WindowsChunkedWriter(getattr(sys, name)))
|
||||||
|
_wrapped_std_streams.add(name)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_text_stdin(buffer_stream):
|
||||||
|
text_stream = _NonClosingTextIOWrapper(
|
||||||
|
io.BufferedReader(_WindowsConsoleReader(STDIN_HANDLE)),
|
||||||
|
'utf-16-le', 'strict', line_buffering=True)
|
||||||
|
return ConsoleStream(text_stream, buffer_stream)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_text_stdout(buffer_stream):
|
||||||
|
text_stream = _NonClosingTextIOWrapper(
|
||||||
|
io.BufferedWriter(_WindowsConsoleWriter(STDOUT_HANDLE)),
|
||||||
|
'utf-16-le', 'strict', line_buffering=True)
|
||||||
|
return ConsoleStream(text_stream, buffer_stream)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_text_stderr(buffer_stream):
|
||||||
|
text_stream = _NonClosingTextIOWrapper(
|
||||||
|
io.BufferedWriter(_WindowsConsoleWriter(STDERR_HANDLE)),
|
||||||
|
'utf-16-le', 'strict', line_buffering=True)
|
||||||
|
return ConsoleStream(text_stream, buffer_stream)
|
||||||
|
|
||||||
|
|
||||||
|
if PY2:
|
||||||
|
def _hash_py_argv():
|
||||||
|
return zlib.crc32('\x00'.join(sys.argv[1:]))
|
||||||
|
|
||||||
|
_initial_argv_hash = _hash_py_argv()
|
||||||
|
|
||||||
|
def _get_windows_argv():
|
||||||
|
argc = c_int(0)
|
||||||
|
argv_unicode = CommandLineToArgvW(GetCommandLineW(), byref(argc))
|
||||||
|
argv = [argv_unicode[i] for i in range(0, argc.value)]
|
||||||
|
|
||||||
|
if not hasattr(sys, 'frozen'):
|
||||||
|
argv = argv[1:]
|
||||||
|
while len(argv) > 0:
|
||||||
|
arg = argv[0]
|
||||||
|
if not arg.startswith('-') or arg == '-':
|
||||||
|
break
|
||||||
|
argv = argv[1:]
|
||||||
|
if arg.startswith(('-c', '-m')):
|
||||||
|
break
|
||||||
|
|
||||||
|
return argv[1:]
|
||||||
|
|
||||||
|
|
||||||
|
_stream_factories = {
|
||||||
|
0: _get_text_stdin,
|
||||||
|
1: _get_text_stdout,
|
||||||
|
2: _get_text_stderr,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _get_windows_console_stream(f, encoding, errors):
|
||||||
|
if get_buffer is not None and \
|
||||||
|
encoding in ('utf-16-le', None) \
|
||||||
|
and errors in ('strict', None) and \
|
||||||
|
hasattr(f, 'isatty') and f.isatty():
|
||||||
|
func = _stream_factories.get(f.fileno())
|
||||||
|
if func is not None:
|
||||||
|
if not PY2:
|
||||||
|
f = getattr(f, 'buffer', None)
|
||||||
|
if f is None:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
# If we are on Python 2 we need to set the stream that we
|
||||||
|
# deal with to binary mode as otherwise the exercise if a
|
||||||
|
# bit moot. The same problems apply as for
|
||||||
|
# get_binary_stdin and friends from _compat.
|
||||||
|
msvcrt.setmode(f.fileno(), os.O_BINARY)
|
||||||
|
return func(f)
|
||||||
1856
venv/lib/python3.6/site-packages/click/core.py
Normal file
1856
venv/lib/python3.6/site-packages/click/core.py
Normal file
File diff suppressed because it is too large
Load Diff
311
venv/lib/python3.6/site-packages/click/decorators.py
Normal file
311
venv/lib/python3.6/site-packages/click/decorators.py
Normal file
@ -0,0 +1,311 @@
|
|||||||
|
import sys
|
||||||
|
import inspect
|
||||||
|
|
||||||
|
from functools import update_wrapper
|
||||||
|
|
||||||
|
from ._compat import iteritems
|
||||||
|
from ._unicodefun import _check_for_unicode_literals
|
||||||
|
from .utils import echo
|
||||||
|
from .globals import get_current_context
|
||||||
|
|
||||||
|
|
||||||
|
def pass_context(f):
|
||||||
|
"""Marks a callback as wanting to receive the current context
|
||||||
|
object as first argument.
|
||||||
|
"""
|
||||||
|
def new_func(*args, **kwargs):
|
||||||
|
return f(get_current_context(), *args, **kwargs)
|
||||||
|
return update_wrapper(new_func, f)
|
||||||
|
|
||||||
|
|
||||||
|
def pass_obj(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):
|
||||||
|
return f(get_current_context().obj, *args, **kwargs)
|
||||||
|
return update_wrapper(new_func, f)
|
||||||
|
|
||||||
|
|
||||||
|
def make_pass_decorator(object_type, ensure=False):
|
||||||
|
"""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):
|
||||||
|
def new_func(*args, **kwargs):
|
||||||
|
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 object of type %r existing'
|
||||||
|
% object_type.__name__)
|
||||||
|
return ctx.invoke(f, obj, *args, **kwargs)
|
||||||
|
return update_wrapper(new_func, f)
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def _make_command(f, name, attrs, cls):
|
||||||
|
if isinstance(f, Command):
|
||||||
|
raise TypeError('Attempted to convert a callback into a '
|
||||||
|
'command twice.')
|
||||||
|
try:
|
||||||
|
params = f.__click_params__
|
||||||
|
params.reverse()
|
||||||
|
del f.__click_params__
|
||||||
|
except AttributeError:
|
||||||
|
params = []
|
||||||
|
help = attrs.get('help')
|
||||||
|
if help is None:
|
||||||
|
help = inspect.getdoc(f)
|
||||||
|
if isinstance(help, bytes):
|
||||||
|
help = help.decode('utf-8')
|
||||||
|
else:
|
||||||
|
help = inspect.cleandoc(help)
|
||||||
|
attrs['help'] = help
|
||||||
|
_check_for_unicode_literals()
|
||||||
|
return cls(name=name or f.__name__.lower().replace('_', '-'),
|
||||||
|
callback=f, params=params, **attrs)
|
||||||
|
|
||||||
|
|
||||||
|
def command(name=None, cls=None, **attrs):
|
||||||
|
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. 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.
|
||||||
|
|
||||||
|
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`.
|
||||||
|
"""
|
||||||
|
if cls is None:
|
||||||
|
cls = Command
|
||||||
|
def decorator(f):
|
||||||
|
cmd = _make_command(f, name, attrs, cls)
|
||||||
|
cmd.__doc__ = f.__doc__
|
||||||
|
return cmd
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def group(name=None, **attrs):
|
||||||
|
"""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`.
|
||||||
|
"""
|
||||||
|
attrs.setdefault('cls', Group)
|
||||||
|
return command(name, **attrs)
|
||||||
|
|
||||||
|
|
||||||
|
def _param_memo(f, param):
|
||||||
|
if isinstance(f, Command):
|
||||||
|
f.params.append(param)
|
||||||
|
else:
|
||||||
|
if not hasattr(f, '__click_params__'):
|
||||||
|
f.__click_params__ = []
|
||||||
|
f.__click_params__.append(param)
|
||||||
|
|
||||||
|
|
||||||
|
def argument(*param_decls, **attrs):
|
||||||
|
"""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):
|
||||||
|
ArgumentClass = attrs.pop('cls', Argument)
|
||||||
|
_param_memo(f, ArgumentClass(param_decls, **attrs))
|
||||||
|
return f
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def option(*param_decls, **attrs):
|
||||||
|
"""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):
|
||||||
|
# Issue 926, copy attrs, so pre-defined options can re-use the same cls=
|
||||||
|
option_attrs = attrs.copy()
|
||||||
|
|
||||||
|
if 'help' in option_attrs:
|
||||||
|
option_attrs['help'] = inspect.cleandoc(option_attrs['help'])
|
||||||
|
OptionClass = option_attrs.pop('cls', Option)
|
||||||
|
_param_memo(f, OptionClass(param_decls, **option_attrs))
|
||||||
|
return f
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def confirmation_option(*param_decls, **attrs):
|
||||||
|
"""Shortcut for confirmation prompts that can be ignored by passing
|
||||||
|
``--yes`` as parameter.
|
||||||
|
|
||||||
|
This is equivalent to decorating a function with :func:`option` with
|
||||||
|
the following parameters::
|
||||||
|
|
||||||
|
def callback(ctx, param, value):
|
||||||
|
if not value:
|
||||||
|
ctx.abort()
|
||||||
|
|
||||||
|
@click.command()
|
||||||
|
@click.option('--yes', is_flag=True, callback=callback,
|
||||||
|
expose_value=False, prompt='Do you want to continue?')
|
||||||
|
def dropdb():
|
||||||
|
pass
|
||||||
|
"""
|
||||||
|
def decorator(f):
|
||||||
|
def callback(ctx, param, value):
|
||||||
|
if not value:
|
||||||
|
ctx.abort()
|
||||||
|
attrs.setdefault('is_flag', True)
|
||||||
|
attrs.setdefault('callback', callback)
|
||||||
|
attrs.setdefault('expose_value', False)
|
||||||
|
attrs.setdefault('prompt', 'Do you want to continue?')
|
||||||
|
attrs.setdefault('help', 'Confirm the action without prompting.')
|
||||||
|
return option(*(param_decls or ('--yes',)), **attrs)(f)
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def password_option(*param_decls, **attrs):
|
||||||
|
"""Shortcut for password prompts.
|
||||||
|
|
||||||
|
This is equivalent to decorating a function with :func:`option` with
|
||||||
|
the following parameters::
|
||||||
|
|
||||||
|
@click.command()
|
||||||
|
@click.option('--password', prompt=True, confirmation_prompt=True,
|
||||||
|
hide_input=True)
|
||||||
|
def changeadmin(password):
|
||||||
|
pass
|
||||||
|
"""
|
||||||
|
def decorator(f):
|
||||||
|
attrs.setdefault('prompt', True)
|
||||||
|
attrs.setdefault('confirmation_prompt', True)
|
||||||
|
attrs.setdefault('hide_input', True)
|
||||||
|
return option(*(param_decls or ('--password',)), **attrs)(f)
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def version_option(version=None, *param_decls, **attrs):
|
||||||
|
"""Adds a ``--version`` option which immediately ends the program
|
||||||
|
printing out the version number. This is implemented as an eager
|
||||||
|
option that prints the version and exits the program in the callback.
|
||||||
|
|
||||||
|
:param version: the version number to show. If not provided Click
|
||||||
|
attempts an auto discovery via setuptools.
|
||||||
|
:param prog_name: the name of the program (defaults to autodetection)
|
||||||
|
:param message: custom message to show instead of the default
|
||||||
|
(``'%(prog)s, version %(version)s'``)
|
||||||
|
:param others: everything else is forwarded to :func:`option`.
|
||||||
|
"""
|
||||||
|
if version is None:
|
||||||
|
if hasattr(sys, '_getframe'):
|
||||||
|
module = sys._getframe(1).f_globals.get('__name__')
|
||||||
|
else:
|
||||||
|
module = ''
|
||||||
|
|
||||||
|
def decorator(f):
|
||||||
|
prog_name = attrs.pop('prog_name', None)
|
||||||
|
message = attrs.pop('message', '%(prog)s, version %(version)s')
|
||||||
|
|
||||||
|
def callback(ctx, param, value):
|
||||||
|
if not value or ctx.resilient_parsing:
|
||||||
|
return
|
||||||
|
prog = prog_name
|
||||||
|
if prog is None:
|
||||||
|
prog = ctx.find_root().info_name
|
||||||
|
ver = version
|
||||||
|
if ver is None:
|
||||||
|
try:
|
||||||
|
import pkg_resources
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
for dist in pkg_resources.working_set:
|
||||||
|
scripts = dist.get_entry_map().get('console_scripts') or {}
|
||||||
|
for script_name, entry_point in iteritems(scripts):
|
||||||
|
if entry_point.module_name == module:
|
||||||
|
ver = dist.version
|
||||||
|
break
|
||||||
|
if ver is None:
|
||||||
|
raise RuntimeError('Could not determine version')
|
||||||
|
echo(message % {
|
||||||
|
'prog': prog,
|
||||||
|
'version': ver,
|
||||||
|
}, color=ctx.color)
|
||||||
|
ctx.exit()
|
||||||
|
|
||||||
|
attrs.setdefault('is_flag', True)
|
||||||
|
attrs.setdefault('expose_value', False)
|
||||||
|
attrs.setdefault('is_eager', True)
|
||||||
|
attrs.setdefault('help', 'Show the version and exit.')
|
||||||
|
attrs['callback'] = callback
|
||||||
|
return option(*(param_decls or ('--version',)), **attrs)(f)
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def help_option(*param_decls, **attrs):
|
||||||
|
"""Adds a ``--help`` option which immediately ends the program
|
||||||
|
printing out the help page. This is usually unnecessary to add as
|
||||||
|
this is added by default to all commands unless suppressed.
|
||||||
|
|
||||||
|
Like :func:`version_option`, this is implemented as eager option that
|
||||||
|
prints in the callback and exits.
|
||||||
|
|
||||||
|
All arguments are forwarded to :func:`option`.
|
||||||
|
"""
|
||||||
|
def decorator(f):
|
||||||
|
def callback(ctx, param, value):
|
||||||
|
if value and not ctx.resilient_parsing:
|
||||||
|
echo(ctx.get_help(), color=ctx.color)
|
||||||
|
ctx.exit()
|
||||||
|
attrs.setdefault('is_flag', True)
|
||||||
|
attrs.setdefault('expose_value', False)
|
||||||
|
attrs.setdefault('help', 'Show this message and exit.')
|
||||||
|
attrs.setdefault('is_eager', True)
|
||||||
|
attrs['callback'] = callback
|
||||||
|
return option(*(param_decls or ('--help',)), **attrs)(f)
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
# Circular dependencies between core and decorators
|
||||||
|
from .core import Command, Group, Argument, Option
|
||||||
235
venv/lib/python3.6/site-packages/click/exceptions.py
Normal file
235
venv/lib/python3.6/site-packages/click/exceptions.py
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
from ._compat import PY2, filename_to_ui, get_text_stderr
|
||||||
|
from .utils import echo
|
||||||
|
|
||||||
|
|
||||||
|
def _join_param_hints(param_hint):
|
||||||
|
if isinstance(param_hint, (tuple, list)):
|
||||||
|
return ' / '.join('"%s"' % 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):
|
||||||
|
ctor_msg = message
|
||||||
|
if PY2:
|
||||||
|
if ctor_msg is not None:
|
||||||
|
ctor_msg = ctor_msg.encode('utf-8')
|
||||||
|
Exception.__init__(self, ctor_msg)
|
||||||
|
self.message = message
|
||||||
|
|
||||||
|
def format_message(self):
|
||||||
|
return self.message
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.message
|
||||||
|
|
||||||
|
if PY2:
|
||||||
|
__unicode__ = __str__
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.message.encode('utf-8')
|
||||||
|
|
||||||
|
def show(self, file=None):
|
||||||
|
if file is None:
|
||||||
|
file = get_text_stderr()
|
||||||
|
echo('Error: %s' % 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, ctx=None):
|
||||||
|
ClickException.__init__(self, message)
|
||||||
|
self.ctx = ctx
|
||||||
|
self.cmd = self.ctx and self.ctx.command or None
|
||||||
|
|
||||||
|
def show(self, file=None):
|
||||||
|
if file is None:
|
||||||
|
file = get_text_stderr()
|
||||||
|
color = None
|
||||||
|
hint = ''
|
||||||
|
if (self.cmd is not None and
|
||||||
|
self.cmd.get_help_option(self.ctx) is not None):
|
||||||
|
hint = ('Try "%s %s" for help.\n'
|
||||||
|
% (self.ctx.command_path, self.ctx.help_option_names[0]))
|
||||||
|
if self.ctx is not None:
|
||||||
|
color = self.ctx.color
|
||||||
|
echo(self.ctx.get_usage() + '\n%s' % hint, file=file, color=color)
|
||||||
|
echo('Error: %s' % 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, ctx=None, param=None,
|
||||||
|
param_hint=None):
|
||||||
|
UsageError.__init__(self, message, ctx)
|
||||||
|
self.param = param
|
||||||
|
self.param_hint = param_hint
|
||||||
|
|
||||||
|
def format_message(self):
|
||||||
|
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)
|
||||||
|
else:
|
||||||
|
return 'Invalid value: %s' % self.message
|
||||||
|
param_hint = _join_param_hints(param_hint)
|
||||||
|
|
||||||
|
return 'Invalid value for %s: %s' % (param_hint, 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=None, ctx=None, param=None,
|
||||||
|
param_hint=None, param_type=None):
|
||||||
|
BadParameter.__init__(self, message, ctx, param, param_hint)
|
||||||
|
self.param_type = param_type
|
||||||
|
|
||||||
|
def format_message(self):
|
||||||
|
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)
|
||||||
|
else:
|
||||||
|
param_hint = None
|
||||||
|
param_hint = _join_param_hints(param_hint)
|
||||||
|
|
||||||
|
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 += '. ' + msg_extra
|
||||||
|
else:
|
||||||
|
msg = msg_extra
|
||||||
|
|
||||||
|
return 'Missing %s%s%s%s' % (
|
||||||
|
param_type,
|
||||||
|
param_hint and ' %s' % param_hint or '',
|
||||||
|
msg and '. ' or '.',
|
||||||
|
msg or '',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class NoSuchOption(UsageError):
|
||||||
|
"""Raised if click attempted to handle an option that does not
|
||||||
|
exist.
|
||||||
|
|
||||||
|
.. versionadded:: 4.0
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, option_name, message=None, possibilities=None,
|
||||||
|
ctx=None):
|
||||||
|
if message is None:
|
||||||
|
message = 'no such option: %s' % option_name
|
||||||
|
UsageError.__init__(self, message, ctx)
|
||||||
|
self.option_name = option_name
|
||||||
|
self.possibilities = possibilities
|
||||||
|
|
||||||
|
def format_message(self):
|
||||||
|
bits = [self.message]
|
||||||
|
if self.possibilities:
|
||||||
|
if len(self.possibilities) == 1:
|
||||||
|
bits.append('Did you mean %s?' % self.possibilities[0])
|
||||||
|
else:
|
||||||
|
possibilities = sorted(self.possibilities)
|
||||||
|
bits.append('(Possible options: %s)' % ', '.join(possibilities))
|
||||||
|
return ' '.join(bits)
|
||||||
|
|
||||||
|
|
||||||
|
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, message, ctx=None):
|
||||||
|
UsageError.__init__(self, 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
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, message, ctx=None):
|
||||||
|
UsageError.__init__(self, message, ctx)
|
||||||
|
|
||||||
|
|
||||||
|
class FileError(ClickException):
|
||||||
|
"""Raised if a file cannot be opened."""
|
||||||
|
|
||||||
|
def __init__(self, filename, hint=None):
|
||||||
|
ui_filename = filename_to_ui(filename)
|
||||||
|
if hint is None:
|
||||||
|
hint = 'unknown error'
|
||||||
|
ClickException.__init__(self, hint)
|
||||||
|
self.ui_filename = ui_filename
|
||||||
|
self.filename = filename
|
||||||
|
|
||||||
|
def format_message(self):
|
||||||
|
return 'Could not open file %s: %s' % (self.ui_filename, 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.
|
||||||
|
"""
|
||||||
|
def __init__(self, code=0):
|
||||||
|
self.exit_code = code
|
||||||
256
venv/lib/python3.6/site-packages/click/formatting.py
Normal file
256
venv/lib/python3.6/site-packages/click/formatting.py
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
from contextlib import contextmanager
|
||||||
|
from .termui import get_terminal_size
|
||||||
|
from .parser import split_opt
|
||||||
|
from ._compat import term_len
|
||||||
|
|
||||||
|
|
||||||
|
# Can force a width. This is used by the test system
|
||||||
|
FORCED_WIDTH = None
|
||||||
|
|
||||||
|
|
||||||
|
def measure_table(rows):
|
||||||
|
widths = {}
|
||||||
|
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, col_count):
|
||||||
|
for row in rows:
|
||||||
|
row = tuple(row)
|
||||||
|
yield row + ('',) * (col_count - len(row))
|
||||||
|
|
||||||
|
|
||||||
|
def wrap_text(text, width=78, initial_indent='', subsequent_indent='',
|
||||||
|
preserve_paragraphs=False):
|
||||||
|
"""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 = []
|
||||||
|
buf = []
|
||||||
|
indent = None
|
||||||
|
|
||||||
|
def _flush_par():
|
||||||
|
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(object):
|
||||||
|
"""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=2, width=None, max_width=None):
|
||||||
|
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(get_terminal_size()[0], max_width) - 2, 50)
|
||||||
|
self.width = width
|
||||||
|
self.current_indent = 0
|
||||||
|
self.buffer = []
|
||||||
|
|
||||||
|
def write(self, string):
|
||||||
|
"""Writes a unicode string into the internal buffer."""
|
||||||
|
self.buffer.append(string)
|
||||||
|
|
||||||
|
def indent(self):
|
||||||
|
"""Increases the indentation."""
|
||||||
|
self.current_indent += self.indent_increment
|
||||||
|
|
||||||
|
def dedent(self):
|
||||||
|
"""Decreases the indentation."""
|
||||||
|
self.current_indent -= self.indent_increment
|
||||||
|
|
||||||
|
def write_usage(self, prog, args='', prefix='Usage: '):
|
||||||
|
"""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.
|
||||||
|
"""
|
||||||
|
usage_prefix = '%*s%s ' % (self.current_indent, prefix, 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):
|
||||||
|
"""Writes a heading into the buffer."""
|
||||||
|
self.write('%*s%s:\n' % (self.current_indent, '', heading))
|
||||||
|
|
||||||
|
def write_paragraph(self):
|
||||||
|
"""Writes a paragraph into the buffer."""
|
||||||
|
if self.buffer:
|
||||||
|
self.write('\n')
|
||||||
|
|
||||||
|
def write_text(self, text):
|
||||||
|
"""Writes re-indented text into the buffer. This rewraps and
|
||||||
|
preserves paragraphs.
|
||||||
|
"""
|
||||||
|
text_width = max(self.width - self.current_indent, 11)
|
||||||
|
indent = ' ' * self.current_indent
|
||||||
|
self.write(wrap_text(text, text_width,
|
||||||
|
initial_indent=indent,
|
||||||
|
subsequent_indent=indent,
|
||||||
|
preserve_paragraphs=True))
|
||||||
|
self.write('\n')
|
||||||
|
|
||||||
|
def write_dl(self, rows, col_max=30, col_spacing=2):
|
||||||
|
"""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('%*s%s' % (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)
|
||||||
|
lines = iter(wrap_text(second, text_width).splitlines())
|
||||||
|
if lines:
|
||||||
|
self.write(next(lines) + '\n')
|
||||||
|
for line in lines:
|
||||||
|
self.write('%*s%s\n' % (
|
||||||
|
first_col + self.current_indent, '', line))
|
||||||
|
else:
|
||||||
|
self.write('\n')
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def section(self, name):
|
||||||
|
"""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):
|
||||||
|
"""A context manager that increases the indentation."""
|
||||||
|
self.indent()
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
self.dedent()
|
||||||
|
|
||||||
|
def getvalue(self):
|
||||||
|
"""Returns the buffer contents."""
|
||||||
|
return ''.join(self.buffer)
|
||||||
|
|
||||||
|
|
||||||
|
def join_options(options):
|
||||||
|
"""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])
|
||||||
|
|
||||||
|
rv = ', '.join(x[1] for x in rv)
|
||||||
|
return rv, any_prefix_is_slash
|
||||||
48
venv/lib/python3.6/site-packages/click/globals.py
Normal file
48
venv/lib/python3.6/site-packages/click/globals.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
from threading import local
|
||||||
|
|
||||||
|
|
||||||
|
_local = local()
|
||||||
|
|
||||||
|
|
||||||
|
def get_current_context(silent=False):
|
||||||
|
"""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: is set to `True` the return value is `None` if no context
|
||||||
|
is available. The default behavior is to raise a
|
||||||
|
:exc:`RuntimeError`.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return getattr(_local, 'stack')[-1]
|
||||||
|
except (AttributeError, IndexError):
|
||||||
|
if not silent:
|
||||||
|
raise RuntimeError('There is no active click context.')
|
||||||
|
|
||||||
|
|
||||||
|
def push_context(ctx):
|
||||||
|
"""Pushes a new context to the current stack."""
|
||||||
|
_local.__dict__.setdefault('stack', []).append(ctx)
|
||||||
|
|
||||||
|
|
||||||
|
def pop_context():
|
||||||
|
"""Removes the top level from the stack."""
|
||||||
|
_local.stack.pop()
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_color_default(color=None):
|
||||||
|
""""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
|
||||||
427
venv/lib/python3.6/site-packages/click/parser.py
Normal file
427
venv/lib/python3.6/site-packages/click/parser.py
Normal file
@ -0,0 +1,427 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
click.parser
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
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.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
from collections import deque
|
||||||
|
from .exceptions import UsageError, NoSuchOption, BadOptionUsage, \
|
||||||
|
BadArgumentUsage
|
||||||
|
|
||||||
|
|
||||||
|
def _unpack_args(args, nargs_spec):
|
||||||
|
"""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 = []
|
||||||
|
spos = None
|
||||||
|
|
||||||
|
def _fetch(c):
|
||||||
|
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 == 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 _error_opt_args(nargs, opt):
|
||||||
|
if nargs == 1:
|
||||||
|
raise BadOptionUsage(opt, '%s option requires an argument' % opt)
|
||||||
|
raise BadOptionUsage(opt, '%s option requires %d arguments' % (opt, nargs))
|
||||||
|
|
||||||
|
|
||||||
|
def split_opt(opt):
|
||||||
|
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, ctx):
|
||||||
|
if ctx is None or ctx.token_normalize_func is None:
|
||||||
|
return opt
|
||||||
|
prefix, opt = split_opt(opt)
|
||||||
|
return prefix + ctx.token_normalize_func(opt)
|
||||||
|
|
||||||
|
|
||||||
|
def split_arg_string(string):
|
||||||
|
"""Given an argument string this attempts to split it into small parts."""
|
||||||
|
rv = []
|
||||||
|
for match in re.finditer(r"('([^'\\]*(?:\\.[^'\\]*)*)'"
|
||||||
|
r'|"([^"\\]*(?:\\.[^"\\]*)*)"'
|
||||||
|
r'|\S+)\s*', string, re.S):
|
||||||
|
arg = match.group().strip()
|
||||||
|
if arg[:1] == arg[-1:] and arg[:1] in '"\'':
|
||||||
|
arg = arg[1:-1].encode('ascii', 'backslashreplace') \
|
||||||
|
.decode('unicode-escape')
|
||||||
|
try:
|
||||||
|
arg = type(string)(arg)
|
||||||
|
except UnicodeError:
|
||||||
|
pass
|
||||||
|
rv.append(arg)
|
||||||
|
return rv
|
||||||
|
|
||||||
|
|
||||||
|
class Option(object):
|
||||||
|
|
||||||
|
def __init__(self, opts, dest, action=None, nargs=1, const=None, obj=None):
|
||||||
|
self._short_opts = []
|
||||||
|
self._long_opts = []
|
||||||
|
self.prefixes = set()
|
||||||
|
|
||||||
|
for opt in opts:
|
||||||
|
prefix, value = split_opt(opt)
|
||||||
|
if not prefix:
|
||||||
|
raise ValueError('Invalid start character for option (%s)'
|
||||||
|
% 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):
|
||||||
|
return self.action in ('store', 'append')
|
||||||
|
|
||||||
|
def process(self, value, state):
|
||||||
|
if self.action == 'store':
|
||||||
|
state.opts[self.dest] = value
|
||||||
|
elif self.action == 'store_const':
|
||||||
|
state.opts[self.dest] = self.const
|
||||||
|
elif self.action == 'append':
|
||||||
|
state.opts.setdefault(self.dest, []).append(value)
|
||||||
|
elif self.action == 'append_const':
|
||||||
|
state.opts.setdefault(self.dest, []).append(self.const)
|
||||||
|
elif self.action == 'count':
|
||||||
|
state.opts[self.dest] = state.opts.get(self.dest, 0) + 1
|
||||||
|
else:
|
||||||
|
raise ValueError('unknown action %r' % self.action)
|
||||||
|
state.order.append(self.obj)
|
||||||
|
|
||||||
|
|
||||||
|
class Argument(object):
|
||||||
|
|
||||||
|
def __init__(self, dest, nargs=1, obj=None):
|
||||||
|
self.dest = dest
|
||||||
|
self.nargs = nargs
|
||||||
|
self.obj = obj
|
||||||
|
|
||||||
|
def process(self, value, state):
|
||||||
|
if self.nargs > 1:
|
||||||
|
holes = sum(1 for x in value if x is None)
|
||||||
|
if holes == len(value):
|
||||||
|
value = None
|
||||||
|
elif holes != 0:
|
||||||
|
raise BadArgumentUsage('argument %s takes %d values'
|
||||||
|
% (self.dest, self.nargs))
|
||||||
|
state.opts[self.dest] = value
|
||||||
|
state.order.append(self.obj)
|
||||||
|
|
||||||
|
|
||||||
|
class ParsingState(object):
|
||||||
|
|
||||||
|
def __init__(self, rargs):
|
||||||
|
self.opts = {}
|
||||||
|
self.largs = []
|
||||||
|
self.rargs = rargs
|
||||||
|
self.order = []
|
||||||
|
|
||||||
|
|
||||||
|
class OptionParser(object):
|
||||||
|
"""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=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 = {}
|
||||||
|
self._long_opt = {}
|
||||||
|
self._opt_prefixes = set(['-', '--'])
|
||||||
|
self._args = []
|
||||||
|
|
||||||
|
def add_option(self, opts, dest, action=None, nargs=1, const=None,
|
||||||
|
obj=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``, ``appnd_const`` or ``count``.
|
||||||
|
|
||||||
|
The `obj` can be used to identify the option in the order list
|
||||||
|
that is returned from the parser.
|
||||||
|
"""
|
||||||
|
if obj is None:
|
||||||
|
obj = dest
|
||||||
|
opts = [normalize_opt(opt, self.ctx) for opt in opts]
|
||||||
|
option = Option(opts, dest, action=action, nargs=nargs,
|
||||||
|
const=const, obj=obj)
|
||||||
|
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, dest, nargs=1, obj=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.
|
||||||
|
"""
|
||||||
|
if obj is None:
|
||||||
|
obj = dest
|
||||||
|
self._args.append(Argument(dest=dest, nargs=nargs, obj=obj))
|
||||||
|
|
||||||
|
def parse_args(self, args):
|
||||||
|
"""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):
|
||||||
|
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):
|
||||||
|
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, explicit_value, state):
|
||||||
|
if opt not in self._long_opt:
|
||||||
|
possibilities = [word for word in self._long_opt
|
||||||
|
if word.startswith(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)
|
||||||
|
|
||||||
|
nargs = option.nargs
|
||||||
|
if len(state.rargs) < nargs:
|
||||||
|
_error_opt_args(nargs, opt)
|
||||||
|
elif nargs == 1:
|
||||||
|
value = state.rargs.pop(0)
|
||||||
|
else:
|
||||||
|
value = tuple(state.rargs[:nargs])
|
||||||
|
del state.rargs[:nargs]
|
||||||
|
|
||||||
|
elif explicit_value is not None:
|
||||||
|
raise BadOptionUsage(opt, '%s option does not take a value' % opt)
|
||||||
|
|
||||||
|
else:
|
||||||
|
value = None
|
||||||
|
|
||||||
|
option.process(value, state)
|
||||||
|
|
||||||
|
def _match_short_opt(self, arg, state):
|
||||||
|
stop = False
|
||||||
|
i = 1
|
||||||
|
prefix = arg[0]
|
||||||
|
unknown_options = []
|
||||||
|
|
||||||
|
for ch in arg[1:]:
|
||||||
|
opt = normalize_opt(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
|
||||||
|
|
||||||
|
nargs = option.nargs
|
||||||
|
if len(state.rargs) < nargs:
|
||||||
|
_error_opt_args(nargs, opt)
|
||||||
|
elif nargs == 1:
|
||||||
|
value = state.rargs.pop(0)
|
||||||
|
else:
|
||||||
|
value = tuple(state.rargs[:nargs])
|
||||||
|
del state.rargs[:nargs]
|
||||||
|
|
||||||
|
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(prefix + ''.join(unknown_options))
|
||||||
|
|
||||||
|
def _process_opts(self, arg, state):
|
||||||
|
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:
|
||||||
|
return self._match_short_opt(arg, state)
|
||||||
|
if not self.ignore_unknown_options:
|
||||||
|
raise
|
||||||
|
state.largs.append(arg)
|
||||||
606
venv/lib/python3.6/site-packages/click/termui.py
Normal file
606
venv/lib/python3.6/site-packages/click/termui.py
Normal file
@ -0,0 +1,606 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import struct
|
||||||
|
import inspect
|
||||||
|
import itertools
|
||||||
|
|
||||||
|
from ._compat import raw_input, text_type, string_types, \
|
||||||
|
isatty, strip_ansi, get_winterm_size, DEFAULT_COLUMNS, WIN
|
||||||
|
from .utils import echo
|
||||||
|
from .exceptions import Abort, UsageError
|
||||||
|
from .types import convert_type, Choice, Path
|
||||||
|
from .globals import resolve_color_default
|
||||||
|
|
||||||
|
|
||||||
|
# The prompt functions to use. The doc tools currently override these
|
||||||
|
# functions to customize how they work.
|
||||||
|
visible_prompt_func = raw_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):
|
||||||
|
import getpass
|
||||||
|
return getpass.getpass(prompt)
|
||||||
|
|
||||||
|
|
||||||
|
def _build_prompt(text, suffix, show_default=False, default=None, show_choices=True, type=None):
|
||||||
|
prompt = text
|
||||||
|
if type is not None and show_choices and isinstance(type, Choice):
|
||||||
|
prompt += ' (' + ", ".join(map(str, type.choices)) + ')'
|
||||||
|
if default is not None and show_default:
|
||||||
|
prompt = '%s [%s]' % (prompt, default)
|
||||||
|
return prompt + suffix
|
||||||
|
|
||||||
|
|
||||||
|
def prompt(text, default=None, hide_input=False, confirmation_prompt=False,
|
||||||
|
type=None, value_proc=None, prompt_suffix=': ', show_default=True,
|
||||||
|
err=False, show_choices=True):
|
||||||
|
"""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 a interrupt signal, this
|
||||||
|
function will catch it and raise a :exc:`Abort` exception.
|
||||||
|
|
||||||
|
.. 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.
|
||||||
|
|
||||||
|
: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: asks for confirmation for the value.
|
||||||
|
: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): ".
|
||||||
|
"""
|
||||||
|
result = None
|
||||||
|
|
||||||
|
def prompt_func(text):
|
||||||
|
f = hide_input and hidden_prompt_func or visible_prompt_func
|
||||||
|
try:
|
||||||
|
# Write the prompt separately so that we get nice
|
||||||
|
# coloring through colorama on Windows
|
||||||
|
echo(text, nl=False, err=err)
|
||||||
|
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()
|
||||||
|
|
||||||
|
if value_proc is None:
|
||||||
|
value_proc = convert_type(type, default)
|
||||||
|
|
||||||
|
prompt = _build_prompt(text, prompt_suffix, show_default, default, show_choices, type)
|
||||||
|
|
||||||
|
while 1:
|
||||||
|
while 1:
|
||||||
|
value = prompt_func(prompt)
|
||||||
|
if value:
|
||||||
|
break
|
||||||
|
elif default is not None:
|
||||||
|
if isinstance(value_proc, Path):
|
||||||
|
# validate Path default value(exists, dir_okay etc.)
|
||||||
|
value = default
|
||||||
|
break
|
||||||
|
return default
|
||||||
|
try:
|
||||||
|
result = value_proc(value)
|
||||||
|
except UsageError as e:
|
||||||
|
echo('Error: %s' % e.message, err=err)
|
||||||
|
continue
|
||||||
|
if not confirmation_prompt:
|
||||||
|
return result
|
||||||
|
while 1:
|
||||||
|
value2 = prompt_func('Repeat for confirmation: ')
|
||||||
|
if value2:
|
||||||
|
break
|
||||||
|
if value == value2:
|
||||||
|
return result
|
||||||
|
echo('Error: the two entered values do not match', err=err)
|
||||||
|
|
||||||
|
|
||||||
|
def confirm(text, default=False, abort=False, prompt_suffix=': ',
|
||||||
|
show_default=True, err=False):
|
||||||
|
"""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.
|
||||||
|
|
||||||
|
.. versionadded:: 4.0
|
||||||
|
Added the `err` parameter.
|
||||||
|
|
||||||
|
:param text: the question to ask.
|
||||||
|
:param default: the default for the prompt.
|
||||||
|
: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.
|
||||||
|
"""
|
||||||
|
prompt = _build_prompt(text, prompt_suffix, show_default,
|
||||||
|
default and 'Y/n' or 'y/N')
|
||||||
|
while 1:
|
||||||
|
try:
|
||||||
|
# Write the prompt separately so that we get nice
|
||||||
|
# coloring through colorama on Windows
|
||||||
|
echo(prompt, nl=False, err=err)
|
||||||
|
value = visible_prompt_func('').lower().strip()
|
||||||
|
except (KeyboardInterrupt, EOFError):
|
||||||
|
raise Abort()
|
||||||
|
if value in ('y', 'yes'):
|
||||||
|
rv = True
|
||||||
|
elif value in ('n', 'no'):
|
||||||
|
rv = False
|
||||||
|
elif value == '':
|
||||||
|
rv = default
|
||||||
|
else:
|
||||||
|
echo('Error: invalid input', err=err)
|
||||||
|
continue
|
||||||
|
break
|
||||||
|
if abort and not rv:
|
||||||
|
raise Abort()
|
||||||
|
return rv
|
||||||
|
|
||||||
|
|
||||||
|
def get_terminal_size():
|
||||||
|
"""Returns the current size of the terminal as tuple in the form
|
||||||
|
``(width, height)`` in columns and rows.
|
||||||
|
"""
|
||||||
|
# If shutil has get_terminal_size() (Python 3.3 and later) use that
|
||||||
|
if sys.version_info >= (3, 3):
|
||||||
|
import shutil
|
||||||
|
shutil_get_terminal_size = getattr(shutil, 'get_terminal_size', None)
|
||||||
|
if shutil_get_terminal_size:
|
||||||
|
sz = shutil_get_terminal_size()
|
||||||
|
return sz.columns, sz.lines
|
||||||
|
|
||||||
|
# We provide a sensible default for get_winterm_size() when being invoked
|
||||||
|
# inside a subprocess. Without this, it would not provide a useful input.
|
||||||
|
if get_winterm_size is not None:
|
||||||
|
size = get_winterm_size()
|
||||||
|
if size == (0, 0):
|
||||||
|
return (79, 24)
|
||||||
|
else:
|
||||||
|
return size
|
||||||
|
|
||||||
|
def ioctl_gwinsz(fd):
|
||||||
|
try:
|
||||||
|
import fcntl
|
||||||
|
import termios
|
||||||
|
cr = struct.unpack(
|
||||||
|
'hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234'))
|
||||||
|
except Exception:
|
||||||
|
return
|
||||||
|
return cr
|
||||||
|
|
||||||
|
cr = ioctl_gwinsz(0) or ioctl_gwinsz(1) or ioctl_gwinsz(2)
|
||||||
|
if not cr:
|
||||||
|
try:
|
||||||
|
fd = os.open(os.ctermid(), os.O_RDONLY)
|
||||||
|
try:
|
||||||
|
cr = ioctl_gwinsz(fd)
|
||||||
|
finally:
|
||||||
|
os.close(fd)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
if not cr or not cr[0] or not cr[1]:
|
||||||
|
cr = (os.environ.get('LINES', 25),
|
||||||
|
os.environ.get('COLUMNS', DEFAULT_COLUMNS))
|
||||||
|
return int(cr[1]), int(cr[0])
|
||||||
|
|
||||||
|
|
||||||
|
def echo_via_pager(text_or_generator, color=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 = text_or_generator()
|
||||||
|
elif isinstance(text_or_generator, string_types):
|
||||||
|
i = [text_or_generator]
|
||||||
|
else:
|
||||||
|
i = iter(text_or_generator)
|
||||||
|
|
||||||
|
# convert every element of i to a text type if necessary
|
||||||
|
text_generator = (el if isinstance(el, string_types) else text_type(el)
|
||||||
|
for el in i)
|
||||||
|
|
||||||
|
from ._termui_impl import pager
|
||||||
|
return pager(itertools.chain(text_generator, "\n"), color)
|
||||||
|
|
||||||
|
|
||||||
|
def progressbar(iterable=None, length=None, label=None, show_eta=True,
|
||||||
|
show_percent=None, show_pos=False,
|
||||||
|
item_show_func=None, fill_char='#', empty_char='-',
|
||||||
|
bar_template='%(label)s [%(bar)s] %(info)s',
|
||||||
|
info_sep=' ', width=36, file=None, color=None):
|
||||||
|
"""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 displayed. 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.
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
.. versionadded:: 4.0
|
||||||
|
Added the `color` parameter. Added a `update` method to the
|
||||||
|
progressbar object.
|
||||||
|
|
||||||
|
: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 the current item
|
||||||
|
next to the progress bar. Note that the current
|
||||||
|
item can be `None`!
|
||||||
|
: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.
|
||||||
|
"""
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
def clear():
|
||||||
|
"""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 we're on Windows and we don't have colorama available, then we
|
||||||
|
# clear the screen by shelling out. Otherwise we can use an escape
|
||||||
|
# sequence.
|
||||||
|
if WIN:
|
||||||
|
os.system('cls')
|
||||||
|
else:
|
||||||
|
sys.stdout.write('\033[2J\033[1;1H')
|
||||||
|
|
||||||
|
|
||||||
|
def style(text, fg=None, bg=None, bold=None, dim=None, underline=None,
|
||||||
|
blink=None, reverse=None, reset=True):
|
||||||
|
"""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'))
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
|
.. versionadded:: 7.0
|
||||||
|
Added support for bright colors.
|
||||||
|
|
||||||
|
: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 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 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.
|
||||||
|
"""
|
||||||
|
bits = []
|
||||||
|
if fg:
|
||||||
|
try:
|
||||||
|
bits.append('\033[%dm' % (_ansi_colors[fg]))
|
||||||
|
except KeyError:
|
||||||
|
raise TypeError('Unknown color %r' % fg)
|
||||||
|
if bg:
|
||||||
|
try:
|
||||||
|
bits.append('\033[%dm' % (_ansi_colors[bg] + 10))
|
||||||
|
except KeyError:
|
||||||
|
raise TypeError('Unknown color %r' % bg)
|
||||||
|
if bold is not None:
|
||||||
|
bits.append('\033[%dm' % (1 if bold else 22))
|
||||||
|
if dim is not None:
|
||||||
|
bits.append('\033[%dm' % (2 if dim else 22))
|
||||||
|
if underline is not None:
|
||||||
|
bits.append('\033[%dm' % (4 if underline else 24))
|
||||||
|
if blink is not None:
|
||||||
|
bits.append('\033[%dm' % (5 if blink else 25))
|
||||||
|
if reverse is not None:
|
||||||
|
bits.append('\033[%dm' % (7 if reverse else 27))
|
||||||
|
bits.append(text)
|
||||||
|
if reset:
|
||||||
|
bits.append(_ansi_reset_all)
|
||||||
|
return ''.join(bits)
|
||||||
|
|
||||||
|
|
||||||
|
def unstyle(text):
|
||||||
|
"""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=None, file=None, nl=True, err=False, color=None, **styles):
|
||||||
|
"""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.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
"""
|
||||||
|
if message is not None:
|
||||||
|
message = style(message, **styles)
|
||||||
|
return echo(message, file=file, nl=nl, err=err, color=color)
|
||||||
|
|
||||||
|
|
||||||
|
def edit(text=None, editor=None, env=None, require_save=True,
|
||||||
|
extension='.txt', filename=None):
|
||||||
|
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
|
||||||
|
editor = Editor(editor=editor, env=env, require_save=require_save,
|
||||||
|
extension=extension)
|
||||||
|
if filename is None:
|
||||||
|
return editor.edit(text)
|
||||||
|
editor.edit_file(filename)
|
||||||
|
|
||||||
|
|
||||||
|
def launch(url, wait=False, locate=False):
|
||||||
|
"""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: waits for the program to stop.
|
||||||
|
: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 = None
|
||||||
|
|
||||||
|
|
||||||
|
def getchar(echo=False):
|
||||||
|
"""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.
|
||||||
|
"""
|
||||||
|
f = _getchar
|
||||||
|
if f is None:
|
||||||
|
from ._termui_impl import getchar as f
|
||||||
|
return f(echo)
|
||||||
|
|
||||||
|
|
||||||
|
def raw_terminal():
|
||||||
|
from ._termui_impl import raw_terminal as f
|
||||||
|
return f()
|
||||||
|
|
||||||
|
|
||||||
|
def pause(info='Press any key to continue ...', err=False):
|
||||||
|
"""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 info string to print before pausing.
|
||||||
|
: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
|
||||||
|
try:
|
||||||
|
if info:
|
||||||
|
echo(info, nl=False, err=err)
|
||||||
|
try:
|
||||||
|
getchar()
|
||||||
|
except (KeyboardInterrupt, EOFError):
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
if info:
|
||||||
|
echo(err=err)
|
||||||
374
venv/lib/python3.6/site-packages/click/testing.py
Normal file
374
venv/lib/python3.6/site-packages/click/testing.py
Normal file
@ -0,0 +1,374 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
|
import contextlib
|
||||||
|
import shlex
|
||||||
|
|
||||||
|
from ._compat import iteritems, PY2, string_types
|
||||||
|
|
||||||
|
|
||||||
|
# If someone wants to vendor click, we want to ensure the
|
||||||
|
# correct package is discovered. Ideally we could use a
|
||||||
|
# relative import here but unfortunately Python does not
|
||||||
|
# support that.
|
||||||
|
clickpkg = sys.modules[__name__.rsplit('.', 1)[0]]
|
||||||
|
|
||||||
|
|
||||||
|
if PY2:
|
||||||
|
from cStringIO import StringIO
|
||||||
|
else:
|
||||||
|
import io
|
||||||
|
from ._compat import _find_binary_reader
|
||||||
|
|
||||||
|
|
||||||
|
class EchoingStdin(object):
|
||||||
|
|
||||||
|
def __init__(self, input, output):
|
||||||
|
self._input = input
|
||||||
|
self._output = output
|
||||||
|
|
||||||
|
def __getattr__(self, x):
|
||||||
|
return getattr(self._input, x)
|
||||||
|
|
||||||
|
def _echo(self, rv):
|
||||||
|
self._output.write(rv)
|
||||||
|
return rv
|
||||||
|
|
||||||
|
def read(self, n=-1):
|
||||||
|
return self._echo(self._input.read(n))
|
||||||
|
|
||||||
|
def readline(self, n=-1):
|
||||||
|
return self._echo(self._input.readline(n))
|
||||||
|
|
||||||
|
def readlines(self):
|
||||||
|
return [self._echo(x) for x in self._input.readlines()]
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(self._echo(x) for x in self._input)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return repr(self._input)
|
||||||
|
|
||||||
|
|
||||||
|
def make_input_stream(input, charset):
|
||||||
|
# Is already an input stream.
|
||||||
|
if hasattr(input, 'read'):
|
||||||
|
if PY2:
|
||||||
|
return input
|
||||||
|
rv = _find_binary_reader(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 not isinstance(input, bytes):
|
||||||
|
input = input.encode(charset)
|
||||||
|
if PY2:
|
||||||
|
return StringIO(input)
|
||||||
|
return io.BytesIO(input)
|
||||||
|
|
||||||
|
|
||||||
|
class Result(object):
|
||||||
|
"""Holds the captured result of an invoked CLI script."""
|
||||||
|
|
||||||
|
def __init__(self, runner, stdout_bytes, stderr_bytes, exit_code,
|
||||||
|
exception, exc_info=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 False(y) if not available
|
||||||
|
self.stderr_bytes = stderr_bytes
|
||||||
|
#: 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):
|
||||||
|
"""The (standard) output as unicode string."""
|
||||||
|
return self.stdout
|
||||||
|
|
||||||
|
@property
|
||||||
|
def stdout(self):
|
||||||
|
"""The standard output as unicode string."""
|
||||||
|
return self.stdout_bytes.decode(self.runner.charset, 'replace') \
|
||||||
|
.replace('\r\n', '\n')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def stderr(self):
|
||||||
|
"""The standard error as unicode string."""
|
||||||
|
if not self.stderr_bytes:
|
||||||
|
raise ValueError("stderr not separately captured")
|
||||||
|
return self.stderr_bytes.decode(self.runner.charset, 'replace') \
|
||||||
|
.replace('\r\n', '\n')
|
||||||
|
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<%s %s>' % (
|
||||||
|
type(self).__name__,
|
||||||
|
self.exception and repr(self.exception) or 'okay',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CliRunner(object):
|
||||||
|
"""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. This is
|
||||||
|
UTF-8 by default and should not be changed currently as
|
||||||
|
the reporting to Click only works in Python 2 properly.
|
||||||
|
: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=None, env=None, echo_stdin=False,
|
||||||
|
mix_stderr=True):
|
||||||
|
if charset is None:
|
||||||
|
charset = 'utf-8'
|
||||||
|
self.charset = charset
|
||||||
|
self.env = env or {}
|
||||||
|
self.echo_stdin = echo_stdin
|
||||||
|
self.mix_stderr = mix_stderr
|
||||||
|
|
||||||
|
def get_default_prog_name(self, cli):
|
||||||
|
"""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=None):
|
||||||
|
"""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=None, env=None, color=False):
|
||||||
|
"""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.
|
||||||
|
|
||||||
|
.. versionadded:: 4.0
|
||||||
|
The ``color`` parameter was added.
|
||||||
|
|
||||||
|
: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.
|
||||||
|
"""
|
||||||
|
input = make_input_stream(input, self.charset)
|
||||||
|
|
||||||
|
old_stdin = sys.stdin
|
||||||
|
old_stdout = sys.stdout
|
||||||
|
old_stderr = sys.stderr
|
||||||
|
old_forced_width = clickpkg.formatting.FORCED_WIDTH
|
||||||
|
clickpkg.formatting.FORCED_WIDTH = 80
|
||||||
|
|
||||||
|
env = self.make_env(env)
|
||||||
|
|
||||||
|
if PY2:
|
||||||
|
bytes_output = StringIO()
|
||||||
|
if self.echo_stdin:
|
||||||
|
input = EchoingStdin(input, bytes_output)
|
||||||
|
sys.stdout = bytes_output
|
||||||
|
if not self.mix_stderr:
|
||||||
|
bytes_error = StringIO()
|
||||||
|
sys.stderr = bytes_error
|
||||||
|
else:
|
||||||
|
bytes_output = io.BytesIO()
|
||||||
|
if self.echo_stdin:
|
||||||
|
input = EchoingStdin(input, bytes_output)
|
||||||
|
input = io.TextIOWrapper(input, encoding=self.charset)
|
||||||
|
sys.stdout = io.TextIOWrapper(
|
||||||
|
bytes_output, encoding=self.charset)
|
||||||
|
if not self.mix_stderr:
|
||||||
|
bytes_error = io.BytesIO()
|
||||||
|
sys.stderr = io.TextIOWrapper(
|
||||||
|
bytes_error, encoding=self.charset)
|
||||||
|
|
||||||
|
if self.mix_stderr:
|
||||||
|
sys.stderr = sys.stdout
|
||||||
|
|
||||||
|
sys.stdin = input
|
||||||
|
|
||||||
|
def visible_input(prompt=None):
|
||||||
|
sys.stdout.write(prompt or '')
|
||||||
|
val = input.readline().rstrip('\r\n')
|
||||||
|
sys.stdout.write(val + '\n')
|
||||||
|
sys.stdout.flush()
|
||||||
|
return val
|
||||||
|
|
||||||
|
def hidden_input(prompt=None):
|
||||||
|
sys.stdout.write((prompt or '') + '\n')
|
||||||
|
sys.stdout.flush()
|
||||||
|
return input.readline().rstrip('\r\n')
|
||||||
|
|
||||||
|
def _getchar(echo):
|
||||||
|
char = sys.stdin.read(1)
|
||||||
|
if echo:
|
||||||
|
sys.stdout.write(char)
|
||||||
|
sys.stdout.flush()
|
||||||
|
return char
|
||||||
|
|
||||||
|
default_color = color
|
||||||
|
|
||||||
|
def should_strip_ansi(stream=None, color=None):
|
||||||
|
if color is None:
|
||||||
|
return not default_color
|
||||||
|
return not color
|
||||||
|
|
||||||
|
old_visible_prompt_func = clickpkg.termui.visible_prompt_func
|
||||||
|
old_hidden_prompt_func = clickpkg.termui.hidden_prompt_func
|
||||||
|
old__getchar_func = clickpkg.termui._getchar
|
||||||
|
old_should_strip_ansi = clickpkg.utils.should_strip_ansi
|
||||||
|
clickpkg.termui.visible_prompt_func = visible_input
|
||||||
|
clickpkg.termui.hidden_prompt_func = hidden_input
|
||||||
|
clickpkg.termui._getchar = _getchar
|
||||||
|
clickpkg.utils.should_strip_ansi = should_strip_ansi
|
||||||
|
|
||||||
|
old_env = {}
|
||||||
|
try:
|
||||||
|
for key, value in iteritems(env):
|
||||||
|
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, not self.mix_stderr and bytes_error)
|
||||||
|
finally:
|
||||||
|
for key, value in iteritems(old_env):
|
||||||
|
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
|
||||||
|
clickpkg.termui.visible_prompt_func = old_visible_prompt_func
|
||||||
|
clickpkg.termui.hidden_prompt_func = old_hidden_prompt_func
|
||||||
|
clickpkg.termui._getchar = old__getchar_func
|
||||||
|
clickpkg.utils.should_strip_ansi = old_should_strip_ansi
|
||||||
|
clickpkg.formatting.FORCED_WIDTH = old_forced_width
|
||||||
|
|
||||||
|
def invoke(self, cli, args=None, input=None, env=None,
|
||||||
|
catch_exceptions=True, color=False, mix_stderr=False, **extra):
|
||||||
|
"""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.
|
||||||
|
|
||||||
|
.. versionadded:: 3.0
|
||||||
|
The ``catch_exceptions`` parameter was added.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.0
|
||||||
|
The result object now has an `exc_info` attribute with the
|
||||||
|
traceback if available.
|
||||||
|
|
||||||
|
.. versionadded:: 4.0
|
||||||
|
The ``color`` parameter was added.
|
||||||
|
|
||||||
|
: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.
|
||||||
|
"""
|
||||||
|
exc_info = None
|
||||||
|
with self.isolation(input=input, env=env, color=color) as outstreams:
|
||||||
|
exception = None
|
||||||
|
exit_code = 0
|
||||||
|
|
||||||
|
if isinstance(args, string_types):
|
||||||
|
args = shlex.split(args)
|
||||||
|
|
||||||
|
try:
|
||||||
|
prog_name = extra.pop("prog_name")
|
||||||
|
except KeyError:
|
||||||
|
prog_name = self.get_default_prog_name(cli)
|
||||||
|
|
||||||
|
try:
|
||||||
|
cli.main(args=args or (), prog_name=prog_name, **extra)
|
||||||
|
except SystemExit as e:
|
||||||
|
exc_info = sys.exc_info()
|
||||||
|
exit_code = e.code
|
||||||
|
if exit_code is None:
|
||||||
|
exit_code = 0
|
||||||
|
|
||||||
|
if exit_code != 0:
|
||||||
|
exception = e
|
||||||
|
|
||||||
|
if not isinstance(exit_code, int):
|
||||||
|
sys.stdout.write(str(exit_code))
|
||||||
|
sys.stdout.write('\n')
|
||||||
|
exit_code = 1
|
||||||
|
|
||||||
|
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()
|
||||||
|
stderr = outstreams[1] and outstreams[1].getvalue()
|
||||||
|
|
||||||
|
return Result(runner=self,
|
||||||
|
stdout_bytes=stdout,
|
||||||
|
stderr_bytes=stderr,
|
||||||
|
exit_code=exit_code,
|
||||||
|
exception=exception,
|
||||||
|
exc_info=exc_info)
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def isolated_filesystem(self):
|
||||||
|
"""A context manager that creates a temporary folder and changes
|
||||||
|
the current working directory to it for isolated filesystem tests.
|
||||||
|
"""
|
||||||
|
cwd = os.getcwd()
|
||||||
|
t = tempfile.mkdtemp()
|
||||||
|
os.chdir(t)
|
||||||
|
try:
|
||||||
|
yield t
|
||||||
|
finally:
|
||||||
|
os.chdir(cwd)
|
||||||
|
try:
|
||||||
|
shutil.rmtree(t)
|
||||||
|
except (OSError, IOError):
|
||||||
|
pass
|
||||||
668
venv/lib/python3.6/site-packages/click/types.py
Normal file
668
venv/lib/python3.6/site-packages/click/types.py
Normal file
@ -0,0 +1,668 @@
|
|||||||
|
import os
|
||||||
|
import stat
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from ._compat import open_stream, text_type, filename_to_ui, \
|
||||||
|
get_filesystem_encoding, get_streerror, _get_argv_encoding, PY2
|
||||||
|
from .exceptions import BadParameter
|
||||||
|
from .utils import safecall, LazyFile
|
||||||
|
|
||||||
|
|
||||||
|
class ParamType(object):
|
||||||
|
"""Helper for converting values through types. The following is
|
||||||
|
necessary for a valid type:
|
||||||
|
|
||||||
|
* it needs a name
|
||||||
|
* it needs to pass through None unchanged
|
||||||
|
* it needs to convert from a string
|
||||||
|
* it needs to convert its result type through unchanged
|
||||||
|
(eg: needs to be idempotent)
|
||||||
|
* it needs to be able to deal with param and context being `None`.
|
||||||
|
This can be the case when the object is used with prompt
|
||||||
|
inputs.
|
||||||
|
"""
|
||||||
|
is_composite = False
|
||||||
|
|
||||||
|
#: the descriptive name of this type
|
||||||
|
name = None
|
||||||
|
|
||||||
|
#: if a list of this type is expected and the value is pulled from a
|
||||||
|
#: string environment variable, this is what splits it up. `None`
|
||||||
|
#: means any whitespace. For all parameters the general rule is that
|
||||||
|
#: whitespace splits them up. The exception are paths and files which
|
||||||
|
#: are split by ``os.path.pathsep`` by default (":" on Unix and ";" on
|
||||||
|
#: Windows).
|
||||||
|
envvar_list_splitter = None
|
||||||
|
|
||||||
|
def __call__(self, value, param=None, ctx=None):
|
||||||
|
if value is not None:
|
||||||
|
return self.convert(value, param, ctx)
|
||||||
|
|
||||||
|
def get_metavar(self, param):
|
||||||
|
"""Returns the metavar default for this param if it provides one."""
|
||||||
|
|
||||||
|
def get_missing_message(self, param):
|
||||||
|
"""Optionally might return extra information about a missing
|
||||||
|
parameter.
|
||||||
|
|
||||||
|
.. versionadded:: 2.0
|
||||||
|
"""
|
||||||
|
|
||||||
|
def convert(self, value, param, ctx):
|
||||||
|
"""Converts the value. This is not invoked for values that are
|
||||||
|
`None` (the missing value).
|
||||||
|
"""
|
||||||
|
return value
|
||||||
|
|
||||||
|
def split_envvar_value(self, rv):
|
||||||
|
"""Given a value from an environment variable this splits it up
|
||||||
|
into small chunks depending on the defined envvar list splitter.
|
||||||
|
|
||||||
|
If the splitter is set to `None`, which means that whitespace splits,
|
||||||
|
then leading and trailing whitespace is ignored. Otherwise, leading
|
||||||
|
and trailing splitters usually lead to empty items being included.
|
||||||
|
"""
|
||||||
|
return (rv or '').split(self.envvar_list_splitter)
|
||||||
|
|
||||||
|
def fail(self, message, param=None, ctx=None):
|
||||||
|
"""Helper method to fail with an invalid value message."""
|
||||||
|
raise BadParameter(message, ctx=ctx, param=param)
|
||||||
|
|
||||||
|
|
||||||
|
class CompositeParamType(ParamType):
|
||||||
|
is_composite = True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def arity(self):
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
|
class FuncParamType(ParamType):
|
||||||
|
|
||||||
|
def __init__(self, func):
|
||||||
|
self.name = func.__name__
|
||||||
|
self.func = func
|
||||||
|
|
||||||
|
def convert(self, value, param, ctx):
|
||||||
|
try:
|
||||||
|
return self.func(value)
|
||||||
|
except ValueError:
|
||||||
|
try:
|
||||||
|
value = text_type(value)
|
||||||
|
except UnicodeError:
|
||||||
|
value = str(value).decode('utf-8', 'replace')
|
||||||
|
self.fail(value, param, ctx)
|
||||||
|
|
||||||
|
|
||||||
|
class UnprocessedParamType(ParamType):
|
||||||
|
name = 'text'
|
||||||
|
|
||||||
|
def convert(self, value, param, ctx):
|
||||||
|
return value
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return 'UNPROCESSED'
|
||||||
|
|
||||||
|
|
||||||
|
class StringParamType(ParamType):
|
||||||
|
name = 'text'
|
||||||
|
|
||||||
|
def convert(self, value, param, ctx):
|
||||||
|
if isinstance(value, bytes):
|
||||||
|
enc = _get_argv_encoding()
|
||||||
|
try:
|
||||||
|
value = value.decode(enc)
|
||||||
|
except UnicodeError:
|
||||||
|
fs_enc = get_filesystem_encoding()
|
||||||
|
if fs_enc != enc:
|
||||||
|
try:
|
||||||
|
value = value.decode(fs_enc)
|
||||||
|
except UnicodeError:
|
||||||
|
value = value.decode('utf-8', 'replace')
|
||||||
|
return value
|
||||||
|
return value
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return 'STRING'
|
||||||
|
|
||||||
|
|
||||||
|
class Choice(ParamType):
|
||||||
|
"""The choice type allows a value to be checked against a fixed set
|
||||||
|
of supported values. All of these values have to be strings.
|
||||||
|
|
||||||
|
You should only pass a list or tuple of choices. Other iterables
|
||||||
|
(like generators) may lead to surprising results.
|
||||||
|
|
||||||
|
See :ref:`choice-opts` for an example.
|
||||||
|
|
||||||
|
:param case_sensitive: Set to false to make choices case
|
||||||
|
insensitive. Defaults to true.
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = 'choice'
|
||||||
|
|
||||||
|
def __init__(self, choices, case_sensitive=True):
|
||||||
|
self.choices = choices
|
||||||
|
self.case_sensitive = case_sensitive
|
||||||
|
|
||||||
|
def get_metavar(self, param):
|
||||||
|
return '[%s]' % '|'.join(self.choices)
|
||||||
|
|
||||||
|
def get_missing_message(self, param):
|
||||||
|
return 'Choose from:\n\t%s.' % ',\n\t'.join(self.choices)
|
||||||
|
|
||||||
|
def convert(self, value, param, ctx):
|
||||||
|
# Exact match
|
||||||
|
if value in self.choices:
|
||||||
|
return value
|
||||||
|
|
||||||
|
# Match through normalization and case sensitivity
|
||||||
|
# first do token_normalize_func, then lowercase
|
||||||
|
# preserve original `value` to produce an accurate message in
|
||||||
|
# `self.fail`
|
||||||
|
normed_value = value
|
||||||
|
normed_choices = self.choices
|
||||||
|
|
||||||
|
if ctx is not None and \
|
||||||
|
ctx.token_normalize_func is not None:
|
||||||
|
normed_value = ctx.token_normalize_func(value)
|
||||||
|
normed_choices = [ctx.token_normalize_func(choice) for choice in
|
||||||
|
self.choices]
|
||||||
|
|
||||||
|
if not self.case_sensitive:
|
||||||
|
normed_value = normed_value.lower()
|
||||||
|
normed_choices = [choice.lower() for choice in normed_choices]
|
||||||
|
|
||||||
|
if normed_value in normed_choices:
|
||||||
|
return normed_value
|
||||||
|
|
||||||
|
self.fail('invalid choice: %s. (choose from %s)' %
|
||||||
|
(value, ', '.join(self.choices)), param, ctx)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return 'Choice(%r)' % list(self.choices)
|
||||||
|
|
||||||
|
|
||||||
|
class DateTime(ParamType):
|
||||||
|
"""The DateTime type converts date strings into `datetime` objects.
|
||||||
|
|
||||||
|
The format strings which are checked are configurable, but default to some
|
||||||
|
common (non-timezone aware) ISO 8601 formats.
|
||||||
|
|
||||||
|
When specifying *DateTime* formats, you should only pass a list or a tuple.
|
||||||
|
Other iterables, like generators, may lead to surprising results.
|
||||||
|
|
||||||
|
The format strings are processed using ``datetime.strptime``, and this
|
||||||
|
consequently defines the format strings which are allowed.
|
||||||
|
|
||||||
|
Parsing is tried using each format, in order, and the first format which
|
||||||
|
parses successfully is used.
|
||||||
|
|
||||||
|
:param formats: A list or tuple of date format strings, in the order in
|
||||||
|
which they should be tried. Defaults to
|
||||||
|
``'%Y-%m-%d'``, ``'%Y-%m-%dT%H:%M:%S'``,
|
||||||
|
``'%Y-%m-%d %H:%M:%S'``.
|
||||||
|
"""
|
||||||
|
name = 'datetime'
|
||||||
|
|
||||||
|
def __init__(self, formats=None):
|
||||||
|
self.formats = formats or [
|
||||||
|
'%Y-%m-%d',
|
||||||
|
'%Y-%m-%dT%H:%M:%S',
|
||||||
|
'%Y-%m-%d %H:%M:%S'
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_metavar(self, param):
|
||||||
|
return '[{}]'.format('|'.join(self.formats))
|
||||||
|
|
||||||
|
def _try_to_convert_date(self, value, format):
|
||||||
|
try:
|
||||||
|
return datetime.strptime(value, format)
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def convert(self, value, param, ctx):
|
||||||
|
# Exact match
|
||||||
|
for format in self.formats:
|
||||||
|
dtime = self._try_to_convert_date(value, format)
|
||||||
|
if dtime:
|
||||||
|
return dtime
|
||||||
|
|
||||||
|
self.fail(
|
||||||
|
'invalid datetime format: {}. (choose from {})'.format(
|
||||||
|
value, ', '.join(self.formats)))
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return 'DateTime'
|
||||||
|
|
||||||
|
|
||||||
|
class IntParamType(ParamType):
|
||||||
|
name = 'integer'
|
||||||
|
|
||||||
|
def convert(self, value, param, ctx):
|
||||||
|
try:
|
||||||
|
return int(value)
|
||||||
|
except (ValueError, UnicodeError):
|
||||||
|
self.fail('%s is not a valid integer' % value, param, ctx)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return 'INT'
|
||||||
|
|
||||||
|
|
||||||
|
class IntRange(IntParamType):
|
||||||
|
"""A parameter that works similar to :data:`click.INT` but restricts
|
||||||
|
the value to fit into a range. The default behavior is to fail if the
|
||||||
|
value falls outside the range, but it can also be silently clamped
|
||||||
|
between the two edges.
|
||||||
|
|
||||||
|
See :ref:`ranges` for an example.
|
||||||
|
"""
|
||||||
|
name = 'integer range'
|
||||||
|
|
||||||
|
def __init__(self, min=None, max=None, clamp=False):
|
||||||
|
self.min = min
|
||||||
|
self.max = max
|
||||||
|
self.clamp = clamp
|
||||||
|
|
||||||
|
def convert(self, value, param, ctx):
|
||||||
|
rv = IntParamType.convert(self, value, param, ctx)
|
||||||
|
if self.clamp:
|
||||||
|
if self.min is not None and rv < self.min:
|
||||||
|
return self.min
|
||||||
|
if self.max is not None and rv > self.max:
|
||||||
|
return self.max
|
||||||
|
if self.min is not None and rv < self.min or \
|
||||||
|
self.max is not None and rv > self.max:
|
||||||
|
if self.min is None:
|
||||||
|
self.fail('%s is bigger than the maximum valid value '
|
||||||
|
'%s.' % (rv, self.max), param, ctx)
|
||||||
|
elif self.max is None:
|
||||||
|
self.fail('%s is smaller than the minimum valid value '
|
||||||
|
'%s.' % (rv, self.min), param, ctx)
|
||||||
|
else:
|
||||||
|
self.fail('%s is not in the valid range of %s to %s.'
|
||||||
|
% (rv, self.min, self.max), param, ctx)
|
||||||
|
return rv
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return 'IntRange(%r, %r)' % (self.min, self.max)
|
||||||
|
|
||||||
|
|
||||||
|
class FloatParamType(ParamType):
|
||||||
|
name = 'float'
|
||||||
|
|
||||||
|
def convert(self, value, param, ctx):
|
||||||
|
try:
|
||||||
|
return float(value)
|
||||||
|
except (UnicodeError, ValueError):
|
||||||
|
self.fail('%s is not a valid floating point value' %
|
||||||
|
value, param, ctx)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return 'FLOAT'
|
||||||
|
|
||||||
|
|
||||||
|
class FloatRange(FloatParamType):
|
||||||
|
"""A parameter that works similar to :data:`click.FLOAT` but restricts
|
||||||
|
the value to fit into a range. The default behavior is to fail if the
|
||||||
|
value falls outside the range, but it can also be silently clamped
|
||||||
|
between the two edges.
|
||||||
|
|
||||||
|
See :ref:`ranges` for an example.
|
||||||
|
"""
|
||||||
|
name = 'float range'
|
||||||
|
|
||||||
|
def __init__(self, min=None, max=None, clamp=False):
|
||||||
|
self.min = min
|
||||||
|
self.max = max
|
||||||
|
self.clamp = clamp
|
||||||
|
|
||||||
|
def convert(self, value, param, ctx):
|
||||||
|
rv = FloatParamType.convert(self, value, param, ctx)
|
||||||
|
if self.clamp:
|
||||||
|
if self.min is not None and rv < self.min:
|
||||||
|
return self.min
|
||||||
|
if self.max is not None and rv > self.max:
|
||||||
|
return self.max
|
||||||
|
if self.min is not None and rv < self.min or \
|
||||||
|
self.max is not None and rv > self.max:
|
||||||
|
if self.min is None:
|
||||||
|
self.fail('%s is bigger than the maximum valid value '
|
||||||
|
'%s.' % (rv, self.max), param, ctx)
|
||||||
|
elif self.max is None:
|
||||||
|
self.fail('%s is smaller than the minimum valid value '
|
||||||
|
'%s.' % (rv, self.min), param, ctx)
|
||||||
|
else:
|
||||||
|
self.fail('%s is not in the valid range of %s to %s.'
|
||||||
|
% (rv, self.min, self.max), param, ctx)
|
||||||
|
return rv
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return 'FloatRange(%r, %r)' % (self.min, self.max)
|
||||||
|
|
||||||
|
|
||||||
|
class BoolParamType(ParamType):
|
||||||
|
name = 'boolean'
|
||||||
|
|
||||||
|
def convert(self, value, param, ctx):
|
||||||
|
if isinstance(value, bool):
|
||||||
|
return bool(value)
|
||||||
|
value = value.lower()
|
||||||
|
if value in ('true', 't', '1', 'yes', 'y'):
|
||||||
|
return True
|
||||||
|
elif value in ('false', 'f', '0', 'no', 'n'):
|
||||||
|
return False
|
||||||
|
self.fail('%s is not a valid boolean' % value, param, ctx)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return 'BOOL'
|
||||||
|
|
||||||
|
|
||||||
|
class UUIDParameterType(ParamType):
|
||||||
|
name = 'uuid'
|
||||||
|
|
||||||
|
def convert(self, value, param, ctx):
|
||||||
|
import uuid
|
||||||
|
try:
|
||||||
|
if PY2 and isinstance(value, text_type):
|
||||||
|
value = value.encode('ascii')
|
||||||
|
return uuid.UUID(value)
|
||||||
|
except (UnicodeError, ValueError):
|
||||||
|
self.fail('%s is not a valid UUID value' % value, param, ctx)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return 'UUID'
|
||||||
|
|
||||||
|
|
||||||
|
class File(ParamType):
|
||||||
|
"""Declares a parameter to be a file for reading or writing. The file
|
||||||
|
is automatically closed once the context tears down (after the command
|
||||||
|
finished working).
|
||||||
|
|
||||||
|
Files can be opened for reading or writing. The special value ``-``
|
||||||
|
indicates stdin or stdout depending on the mode.
|
||||||
|
|
||||||
|
By default, the file is opened for reading text data, but it can also be
|
||||||
|
opened in binary mode or for writing. The encoding parameter can be used
|
||||||
|
to force a specific encoding.
|
||||||
|
|
||||||
|
The `lazy` flag controls if the file should be opened immediately or upon
|
||||||
|
first IO. The default is to be non-lazy for standard input and output
|
||||||
|
streams as well as files opened for reading, `lazy` otherwise. When opening a
|
||||||
|
file lazily for reading, it is still opened temporarily for validation, but
|
||||||
|
will not be held open until first IO. lazy is mainly useful when opening
|
||||||
|
for writing to avoid creating the file until it is needed.
|
||||||
|
|
||||||
|
Starting with Click 2.0, files can also be opened atomically in which
|
||||||
|
case all writes go into a separate file in the same folder and upon
|
||||||
|
completion the file will be moved over to the original location. This
|
||||||
|
is useful if a file regularly read by other users is modified.
|
||||||
|
|
||||||
|
See :ref:`file-args` for more information.
|
||||||
|
"""
|
||||||
|
name = 'filename'
|
||||||
|
envvar_list_splitter = os.path.pathsep
|
||||||
|
|
||||||
|
def __init__(self, mode='r', encoding=None, errors='strict', lazy=None,
|
||||||
|
atomic=False):
|
||||||
|
self.mode = mode
|
||||||
|
self.encoding = encoding
|
||||||
|
self.errors = errors
|
||||||
|
self.lazy = lazy
|
||||||
|
self.atomic = atomic
|
||||||
|
|
||||||
|
def resolve_lazy_flag(self, value):
|
||||||
|
if self.lazy is not None:
|
||||||
|
return self.lazy
|
||||||
|
if value == '-':
|
||||||
|
return False
|
||||||
|
elif 'w' in self.mode:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def convert(self, value, param, ctx):
|
||||||
|
try:
|
||||||
|
if hasattr(value, 'read') or hasattr(value, 'write'):
|
||||||
|
return value
|
||||||
|
|
||||||
|
lazy = self.resolve_lazy_flag(value)
|
||||||
|
|
||||||
|
if lazy:
|
||||||
|
f = LazyFile(value, self.mode, self.encoding, self.errors,
|
||||||
|
atomic=self.atomic)
|
||||||
|
if ctx is not None:
|
||||||
|
ctx.call_on_close(f.close_intelligently)
|
||||||
|
return f
|
||||||
|
|
||||||
|
f, should_close = open_stream(value, self.mode,
|
||||||
|
self.encoding, self.errors,
|
||||||
|
atomic=self.atomic)
|
||||||
|
# If a context is provided, we automatically close the file
|
||||||
|
# at the end of the context execution (or flush out). If a
|
||||||
|
# context does not exist, it's the caller's responsibility to
|
||||||
|
# properly close the file. This for instance happens when the
|
||||||
|
# type is used with prompts.
|
||||||
|
if ctx is not None:
|
||||||
|
if should_close:
|
||||||
|
ctx.call_on_close(safecall(f.close))
|
||||||
|
else:
|
||||||
|
ctx.call_on_close(safecall(f.flush))
|
||||||
|
return f
|
||||||
|
except (IOError, OSError) as e:
|
||||||
|
self.fail('Could not open file: %s: %s' % (
|
||||||
|
filename_to_ui(value),
|
||||||
|
get_streerror(e),
|
||||||
|
), param, ctx)
|
||||||
|
|
||||||
|
|
||||||
|
class Path(ParamType):
|
||||||
|
"""The path type is similar to the :class:`File` type but it performs
|
||||||
|
different checks. First of all, instead of returning an open file
|
||||||
|
handle it returns just the filename. Secondly, it can perform various
|
||||||
|
basic checks about what the file or directory should be.
|
||||||
|
|
||||||
|
.. versionchanged:: 6.0
|
||||||
|
`allow_dash` was added.
|
||||||
|
|
||||||
|
:param exists: if set to true, the file or directory needs to exist for
|
||||||
|
this value to be valid. If this is not required and a
|
||||||
|
file does indeed not exist, then all further checks are
|
||||||
|
silently skipped.
|
||||||
|
:param file_okay: controls if a file is a possible value.
|
||||||
|
:param dir_okay: controls if a directory is a possible value.
|
||||||
|
:param writable: if true, a writable check is performed.
|
||||||
|
:param readable: if true, a readable check is performed.
|
||||||
|
:param resolve_path: if this is true, then the path is fully resolved
|
||||||
|
before the value is passed onwards. This means
|
||||||
|
that it's absolute and symlinks are resolved. It
|
||||||
|
will not expand a tilde-prefix, as this is
|
||||||
|
supposed to be done by the shell only.
|
||||||
|
:param allow_dash: If this is set to `True`, a single dash to indicate
|
||||||
|
standard streams is permitted.
|
||||||
|
:param path_type: optionally a string type that should be used to
|
||||||
|
represent the path. The default is `None` which
|
||||||
|
means the return value will be either bytes or
|
||||||
|
unicode depending on what makes most sense given the
|
||||||
|
input data Click deals with.
|
||||||
|
"""
|
||||||
|
envvar_list_splitter = os.path.pathsep
|
||||||
|
|
||||||
|
def __init__(self, exists=False, file_okay=True, dir_okay=True,
|
||||||
|
writable=False, readable=True, resolve_path=False,
|
||||||
|
allow_dash=False, path_type=None):
|
||||||
|
self.exists = exists
|
||||||
|
self.file_okay = file_okay
|
||||||
|
self.dir_okay = dir_okay
|
||||||
|
self.writable = writable
|
||||||
|
self.readable = readable
|
||||||
|
self.resolve_path = resolve_path
|
||||||
|
self.allow_dash = allow_dash
|
||||||
|
self.type = path_type
|
||||||
|
|
||||||
|
if self.file_okay and not self.dir_okay:
|
||||||
|
self.name = 'file'
|
||||||
|
self.path_type = 'File'
|
||||||
|
elif self.dir_okay and not self.file_okay:
|
||||||
|
self.name = 'directory'
|
||||||
|
self.path_type = 'Directory'
|
||||||
|
else:
|
||||||
|
self.name = 'path'
|
||||||
|
self.path_type = 'Path'
|
||||||
|
|
||||||
|
def coerce_path_result(self, rv):
|
||||||
|
if self.type is not None and not isinstance(rv, self.type):
|
||||||
|
if self.type is text_type:
|
||||||
|
rv = rv.decode(get_filesystem_encoding())
|
||||||
|
else:
|
||||||
|
rv = rv.encode(get_filesystem_encoding())
|
||||||
|
return rv
|
||||||
|
|
||||||
|
def convert(self, value, param, ctx):
|
||||||
|
rv = value
|
||||||
|
|
||||||
|
is_dash = self.file_okay and self.allow_dash and rv in (b'-', '-')
|
||||||
|
|
||||||
|
if not is_dash:
|
||||||
|
if self.resolve_path:
|
||||||
|
rv = os.path.realpath(rv)
|
||||||
|
|
||||||
|
try:
|
||||||
|
st = os.stat(rv)
|
||||||
|
except OSError:
|
||||||
|
if not self.exists:
|
||||||
|
return self.coerce_path_result(rv)
|
||||||
|
self.fail('%s "%s" does not exist.' % (
|
||||||
|
self.path_type,
|
||||||
|
filename_to_ui(value)
|
||||||
|
), param, ctx)
|
||||||
|
|
||||||
|
if not self.file_okay and stat.S_ISREG(st.st_mode):
|
||||||
|
self.fail('%s "%s" is a file.' % (
|
||||||
|
self.path_type,
|
||||||
|
filename_to_ui(value)
|
||||||
|
), param, ctx)
|
||||||
|
if not self.dir_okay and stat.S_ISDIR(st.st_mode):
|
||||||
|
self.fail('%s "%s" is a directory.' % (
|
||||||
|
self.path_type,
|
||||||
|
filename_to_ui(value)
|
||||||
|
), param, ctx)
|
||||||
|
if self.writable and not os.access(value, os.W_OK):
|
||||||
|
self.fail('%s "%s" is not writable.' % (
|
||||||
|
self.path_type,
|
||||||
|
filename_to_ui(value)
|
||||||
|
), param, ctx)
|
||||||
|
if self.readable and not os.access(value, os.R_OK):
|
||||||
|
self.fail('%s "%s" is not readable.' % (
|
||||||
|
self.path_type,
|
||||||
|
filename_to_ui(value)
|
||||||
|
), param, ctx)
|
||||||
|
|
||||||
|
return self.coerce_path_result(rv)
|
||||||
|
|
||||||
|
|
||||||
|
class Tuple(CompositeParamType):
|
||||||
|
"""The default behavior of Click is to apply a type on a value directly.
|
||||||
|
This works well in most cases, except for when `nargs` is set to a fixed
|
||||||
|
count and different types should be used for different items. In this
|
||||||
|
case the :class:`Tuple` type can be used. This type can only be used
|
||||||
|
if `nargs` is set to a fixed number.
|
||||||
|
|
||||||
|
For more information see :ref:`tuple-type`.
|
||||||
|
|
||||||
|
This can be selected by using a Python tuple literal as a type.
|
||||||
|
|
||||||
|
:param types: a list of types that should be used for the tuple items.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, types):
|
||||||
|
self.types = [convert_type(ty) for ty in types]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
return "<" + " ".join(ty.name for ty in self.types) + ">"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def arity(self):
|
||||||
|
return len(self.types)
|
||||||
|
|
||||||
|
def convert(self, value, param, ctx):
|
||||||
|
if len(value) != len(self.types):
|
||||||
|
raise TypeError('It would appear that nargs is set to conflict '
|
||||||
|
'with the composite type arity.')
|
||||||
|
return tuple(ty(x, param, ctx) for ty, x in zip(self.types, value))
|
||||||
|
|
||||||
|
|
||||||
|
def convert_type(ty, default=None):
|
||||||
|
"""Converts a callable or python ty into the most appropriate param
|
||||||
|
ty.
|
||||||
|
"""
|
||||||
|
guessed_type = False
|
||||||
|
if ty is None and default is not None:
|
||||||
|
if isinstance(default, tuple):
|
||||||
|
ty = tuple(map(type, default))
|
||||||
|
else:
|
||||||
|
ty = type(default)
|
||||||
|
guessed_type = True
|
||||||
|
|
||||||
|
if isinstance(ty, tuple):
|
||||||
|
return Tuple(ty)
|
||||||
|
if isinstance(ty, ParamType):
|
||||||
|
return ty
|
||||||
|
if ty is text_type or ty is str or ty is None:
|
||||||
|
return STRING
|
||||||
|
if ty is int:
|
||||||
|
return INT
|
||||||
|
# Booleans are only okay if not guessed. This is done because for
|
||||||
|
# flags the default value is actually a bit of a lie in that it
|
||||||
|
# indicates which of the flags is the one we want. See get_default()
|
||||||
|
# for more information.
|
||||||
|
if ty is bool and not guessed_type:
|
||||||
|
return BOOL
|
||||||
|
if ty is float:
|
||||||
|
return FLOAT
|
||||||
|
if guessed_type:
|
||||||
|
return STRING
|
||||||
|
|
||||||
|
# Catch a common mistake
|
||||||
|
if __debug__:
|
||||||
|
try:
|
||||||
|
if issubclass(ty, ParamType):
|
||||||
|
raise AssertionError('Attempted to use an uninstantiated '
|
||||||
|
'parameter type (%s).' % ty)
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
return FuncParamType(ty)
|
||||||
|
|
||||||
|
|
||||||
|
#: A dummy parameter type that just does nothing. From a user's
|
||||||
|
#: perspective this appears to just be the same as `STRING` but internally
|
||||||
|
#: no string conversion takes place. This is necessary to achieve the
|
||||||
|
#: same bytes/unicode behavior on Python 2/3 in situations where you want
|
||||||
|
#: to not convert argument types. This is usually useful when working
|
||||||
|
#: with file paths as they can appear in bytes and unicode.
|
||||||
|
#:
|
||||||
|
#: For path related uses the :class:`Path` type is a better choice but
|
||||||
|
#: there are situations where an unprocessed type is useful which is why
|
||||||
|
#: it is is provided.
|
||||||
|
#:
|
||||||
|
#: .. versionadded:: 4.0
|
||||||
|
UNPROCESSED = UnprocessedParamType()
|
||||||
|
|
||||||
|
#: A unicode string parameter type which is the implicit default. This
|
||||||
|
#: can also be selected by using ``str`` as type.
|
||||||
|
STRING = StringParamType()
|
||||||
|
|
||||||
|
#: An integer parameter. This can also be selected by using ``int`` as
|
||||||
|
#: type.
|
||||||
|
INT = IntParamType()
|
||||||
|
|
||||||
|
#: A floating point value parameter. This can also be selected by using
|
||||||
|
#: ``float`` as type.
|
||||||
|
FLOAT = FloatParamType()
|
||||||
|
|
||||||
|
#: A boolean parameter. This is the default for boolean flags. This can
|
||||||
|
#: also be selected by using ``bool`` as a type.
|
||||||
|
BOOL = BoolParamType()
|
||||||
|
|
||||||
|
#: A UUID parameter.
|
||||||
|
UUID = UUIDParameterType()
|
||||||
440
venv/lib/python3.6/site-packages/click/utils.py
Normal file
440
venv/lib/python3.6/site-packages/click/utils.py
Normal file
@ -0,0 +1,440 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from .globals import resolve_color_default
|
||||||
|
|
||||||
|
from ._compat import text_type, open_stream, get_filesystem_encoding, \
|
||||||
|
get_streerror, string_types, PY2, binary_streams, text_streams, \
|
||||||
|
filename_to_ui, auto_wrap_for_ansi, strip_ansi, should_strip_ansi, \
|
||||||
|
_default_text_stdout, _default_text_stderr, is_bytes, WIN
|
||||||
|
|
||||||
|
if not PY2:
|
||||||
|
from ._compat import _find_binary_writer
|
||||||
|
elif WIN:
|
||||||
|
from ._winconsole import _get_windows_argv, \
|
||||||
|
_hash_py_argv, _initial_argv_hash
|
||||||
|
|
||||||
|
|
||||||
|
echo_native_types = string_types + (bytes, bytearray)
|
||||||
|
|
||||||
|
|
||||||
|
def _posixify(name):
|
||||||
|
return '-'.join(name.split()).lower()
|
||||||
|
|
||||||
|
|
||||||
|
def safecall(func):
|
||||||
|
"""Wraps a function so that it swallows exceptions."""
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
try:
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def make_str(value):
|
||||||
|
"""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 text_type(value)
|
||||||
|
|
||||||
|
|
||||||
|
def make_default_short_help(help, max_length=45):
|
||||||
|
"""Return a condensed version of help string."""
|
||||||
|
words = help.split()
|
||||||
|
total_length = 0
|
||||||
|
result = []
|
||||||
|
done = False
|
||||||
|
|
||||||
|
for word in words:
|
||||||
|
if word[-1:] == '.':
|
||||||
|
done = True
|
||||||
|
new_length = result and 1 + len(word) or len(word)
|
||||||
|
if total_length + new_length > max_length:
|
||||||
|
result.append('...')
|
||||||
|
done = True
|
||||||
|
else:
|
||||||
|
if result:
|
||||||
|
result.append(' ')
|
||||||
|
result.append(word)
|
||||||
|
if done:
|
||||||
|
break
|
||||||
|
total_length += new_length
|
||||||
|
|
||||||
|
return ''.join(result)
|
||||||
|
|
||||||
|
|
||||||
|
class LazyFile(object):
|
||||||
|
"""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, mode='r', encoding=None, errors='strict',
|
||||||
|
atomic=False):
|
||||||
|
self.name = filename
|
||||||
|
self.mode = mode
|
||||||
|
self.encoding = encoding
|
||||||
|
self.errors = errors
|
||||||
|
self.atomic = atomic
|
||||||
|
|
||||||
|
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):
|
||||||
|
return getattr(self.open(), name)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
if self._f is not None:
|
||||||
|
return repr(self._f)
|
||||||
|
return '<unopened file %r %s>' % (self.name, self.mode)
|
||||||
|
|
||||||
|
def open(self):
|
||||||
|
"""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 (IOError, OSError) as e:
|
||||||
|
from .exceptions import FileError
|
||||||
|
raise FileError(self.name, hint=get_streerror(e))
|
||||||
|
self._f = rv
|
||||||
|
return rv
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
"""Closes the underlying file, no matter what."""
|
||||||
|
if self._f is not None:
|
||||||
|
self._f.close()
|
||||||
|
|
||||||
|
def close_intelligently(self):
|
||||||
|
"""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):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_value, tb):
|
||||||
|
self.close_intelligently()
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
self.open()
|
||||||
|
return iter(self._f)
|
||||||
|
|
||||||
|
|
||||||
|
class KeepOpenFile(object):
|
||||||
|
|
||||||
|
def __init__(self, file):
|
||||||
|
self._file = file
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
return getattr(self._file, name)
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_value, tb):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return repr(self._file)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(self._file)
|
||||||
|
|
||||||
|
|
||||||
|
def echo(message=None, file=None, nl=True, err=False, color=None):
|
||||||
|
"""Prints a message plus a newline to the given file or stdout. On
|
||||||
|
first sight, this looks like the print function, but it has improved
|
||||||
|
support for handling Unicode and binary data that does not fail no
|
||||||
|
matter how badly configured the system is.
|
||||||
|
|
||||||
|
Primarily it means that you can print binary data as well as Unicode
|
||||||
|
data on both 2.x and 3.x to the given file in the most appropriate way
|
||||||
|
possible. This is a very carefree function in that it will try its
|
||||||
|
best to not fail. As of Click 6.0 this includes support for unicode
|
||||||
|
output on the Windows console.
|
||||||
|
|
||||||
|
In addition to that, if `colorama`_ is installed, the echo function will
|
||||||
|
also support clever handling of ANSI codes. Essentially it will then
|
||||||
|
do the following:
|
||||||
|
|
||||||
|
- add transparent handling of ANSI color codes on Windows.
|
||||||
|
- hide ANSI codes automatically if the destination file is not a
|
||||||
|
terminal.
|
||||||
|
|
||||||
|
.. _colorama: https://pypi.org/project/colorama/
|
||||||
|
|
||||||
|
.. versionchanged:: 6.0
|
||||||
|
As of Click 6.0 the echo function will properly support unicode
|
||||||
|
output on the windows console. Not that click does not modify
|
||||||
|
the interpreter in any way which means that `sys.stdout` or the
|
||||||
|
print statement or function will still not provide unicode support.
|
||||||
|
|
||||||
|
.. versionchanged:: 2.0
|
||||||
|
Starting with version 2.0 of Click, the echo function will work
|
||||||
|
with colorama if it's installed.
|
||||||
|
|
||||||
|
.. versionadded:: 3.0
|
||||||
|
The `err` parameter was added.
|
||||||
|
|
||||||
|
.. versionchanged:: 4.0
|
||||||
|
Added the `color` flag.
|
||||||
|
|
||||||
|
:param message: the message to print
|
||||||
|
:param file: the file to write to (defaults to ``stdout``)
|
||||||
|
:param err: if set to true the file defaults to ``stderr`` instead of
|
||||||
|
``stdout``. This is faster and easier than calling
|
||||||
|
:func:`get_text_stderr` yourself.
|
||||||
|
:param nl: if set to `True` (the default) a newline is printed afterwards.
|
||||||
|
:param color: controls if the terminal supports ANSI colors or not. The
|
||||||
|
default is autodetection.
|
||||||
|
"""
|
||||||
|
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, echo_native_types):
|
||||||
|
message = text_type(message)
|
||||||
|
|
||||||
|
if nl:
|
||||||
|
message = message or u''
|
||||||
|
if isinstance(message, text_type):
|
||||||
|
message += u'\n'
|
||||||
|
else:
|
||||||
|
message += b'\n'
|
||||||
|
|
||||||
|
# If there is a message, and we're in Python 3, 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 message and not PY2 and is_bytes(message):
|
||||||
|
binary_file = _find_binary_writer(file)
|
||||||
|
if binary_file is not None:
|
||||||
|
file.flush()
|
||||||
|
binary_file.write(message)
|
||||||
|
binary_file.flush()
|
||||||
|
return
|
||||||
|
|
||||||
|
# ANSI-style support. If there is no message or we are dealing with
|
||||||
|
# bytes nothing is happening. If we are connected to a file we want
|
||||||
|
# to strip colors. If we are on windows we either wrap the stream
|
||||||
|
# to strip the color or we use the colorama support to translate the
|
||||||
|
# ansi codes to API calls.
|
||||||
|
if message and not is_bytes(message):
|
||||||
|
color = resolve_color_default(color)
|
||||||
|
if should_strip_ansi(file, color):
|
||||||
|
message = strip_ansi(message)
|
||||||
|
elif WIN:
|
||||||
|
if auto_wrap_for_ansi is not None:
|
||||||
|
file = auto_wrap_for_ansi(file)
|
||||||
|
elif not color:
|
||||||
|
message = strip_ansi(message)
|
||||||
|
|
||||||
|
if message:
|
||||||
|
file.write(message)
|
||||||
|
file.flush()
|
||||||
|
|
||||||
|
|
||||||
|
def get_binary_stream(name):
|
||||||
|
"""Returns a system stream for byte processing. This essentially
|
||||||
|
returns the stream from the sys module with the given name but it
|
||||||
|
solves some compatibility issues between different Python versions.
|
||||||
|
Primarily this function is necessary for getting binary streams on
|
||||||
|
Python 3.
|
||||||
|
|
||||||
|
: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('Unknown standard stream %r' % name)
|
||||||
|
return opener()
|
||||||
|
|
||||||
|
|
||||||
|
def get_text_stream(name, encoding=None, errors='strict'):
|
||||||
|
"""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 on Python 3
|
||||||
|
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('Unknown standard stream %r' % name)
|
||||||
|
return opener(encoding, errors)
|
||||||
|
|
||||||
|
|
||||||
|
def open_file(filename, mode='r', encoding=None, errors='strict',
|
||||||
|
lazy=False, atomic=False):
|
||||||
|
"""This is similar to how the :class:`File` works but for manual
|
||||||
|
usage. Files are opened non lazy by default. This can open regular
|
||||||
|
files as well as stdin/stdout if ``'-'`` is passed.
|
||||||
|
|
||||||
|
If stdin/stdout is returned the stream is wrapped so that the context
|
||||||
|
manager will not close the stream accidentally. This makes it possible
|
||||||
|
to always use the function like this without having to worry to
|
||||||
|
accidentally close a standard stream::
|
||||||
|
|
||||||
|
with open_file(filename) as f:
|
||||||
|
...
|
||||||
|
|
||||||
|
.. versionadded:: 3.0
|
||||||
|
|
||||||
|
: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 use.
|
||||||
|
:param errors: the error handling for this file.
|
||||||
|
:param lazy: can be flipped to true to open the file lazily.
|
||||||
|
:param atomic: in atomic mode writes go into a temporary file and it's
|
||||||
|
moved on close.
|
||||||
|
"""
|
||||||
|
if lazy:
|
||||||
|
return LazyFile(filename, mode, encoding, errors, atomic=atomic)
|
||||||
|
f, should_close = open_stream(filename, mode, encoding, errors,
|
||||||
|
atomic=atomic)
|
||||||
|
if not should_close:
|
||||||
|
f = KeepOpenFile(f)
|
||||||
|
return f
|
||||||
|
|
||||||
|
|
||||||
|
def get_os_args():
|
||||||
|
"""This returns the argument part of sys.argv in the most appropriate
|
||||||
|
form for processing. What this means is that this return value is in
|
||||||
|
a format that works for Click to process but does not necessarily
|
||||||
|
correspond well to what's actually standard for the interpreter.
|
||||||
|
|
||||||
|
On most environments the return value is ``sys.argv[:1]`` unchanged.
|
||||||
|
However if you are on Windows and running Python 2 the return value
|
||||||
|
will actually be a list of unicode strings instead because the
|
||||||
|
default behavior on that platform otherwise will not be able to
|
||||||
|
carry all possible values that sys.argv can have.
|
||||||
|
|
||||||
|
.. versionadded:: 6.0
|
||||||
|
"""
|
||||||
|
# We can only extract the unicode argv if sys.argv has not been
|
||||||
|
# changed since the startup of the application.
|
||||||
|
if PY2 and WIN and _initial_argv_hash == _hash_py_argv():
|
||||||
|
return _get_windows_argv()
|
||||||
|
return sys.argv[1:]
|
||||||
|
|
||||||
|
|
||||||
|
def format_filename(filename, shorten=False):
|
||||||
|
"""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 filename_to_ui(filename)
|
||||||
|
|
||||||
|
|
||||||
|
def get_app_dir(app_name, roaming=True, force_posix=False):
|
||||||
|
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``
|
||||||
|
Win XP (roaming):
|
||||||
|
``C:\Documents and Settings\<user>\Local Settings\Application Data\Foo Bar``
|
||||||
|
Win XP (not roaming):
|
||||||
|
``C:\Documents and Settings\<user>\Application Data\Foo Bar``
|
||||||
|
Win 7 (roaming):
|
||||||
|
``C:\Users\<user>\AppData\Roaming\Foo Bar``
|
||||||
|
Win 7 (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 = roaming and 'APPDATA' or '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('~/.' + _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(object):
|
||||||
|
"""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):
|
||||||
|
self.wrapped = wrapped
|
||||||
|
|
||||||
|
def flush(self):
|
||||||
|
try:
|
||||||
|
self.wrapped.flush()
|
||||||
|
except IOError as e:
|
||||||
|
import errno
|
||||||
|
if e.errno != errno.EPIPE:
|
||||||
|
raise
|
||||||
|
|
||||||
|
def __getattr__(self, attr):
|
||||||
|
return getattr(self.wrapped, attr)
|
||||||
@ -0,0 +1,450 @@
|
|||||||
|
``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).
|
||||||
|
|
||||||
|
|
||||||
@ -0,0 +1 @@
|
|||||||
|
pip
|
||||||
469
venv/lib/python3.6/site-packages/docopt-0.6.2.dist-info/METADATA
Normal file
469
venv/lib/python3.6/site-packages/docopt-0.6.2.dist-info/METADATA
Normal file
@ -0,0 +1,469 @@
|
|||||||
|
Metadata-Version: 2.0
|
||||||
|
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
|
||||||
|
|
||||||
|
``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).
|
||||||
|
|
||||||
|
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
docopt.py,sha256=RMZQ69gz2FLIcx-j8MV1lQYwliIwDkwZVKVA14VyzFQ,19946
|
||||||
|
docopt-0.6.2.dist-info/DESCRIPTION.rst,sha256=LUuk6x_Mlk0p6LdKL7khsZLqAQJ8eblf5WFJfJ4HYmo,17261
|
||||||
|
docopt-0.6.2.dist-info/METADATA,sha256=Bx9U0oJrkKGRfXj_rZIL_M-slIOlzMelDdqK3yhG3jg,17930
|
||||||
|
docopt-0.6.2.dist-info/RECORD,,
|
||||||
|
docopt-0.6.2.dist-info/WHEEL,sha256=o2k-Qa-RMNIJmUdIc7KU6VWR_ErNRbWNlxDIpl7lm34,110
|
||||||
|
docopt-0.6.2.dist-info/metadata.json,sha256=i3QPxtguenkllKC10loruJls1EQzfSCN3H660ejIoGU,822
|
||||||
|
docopt-0.6.2.dist-info/top_level.txt,sha256=xAvL2ywTOdLde8wxTVye1299j65YdK3cM5963wNy5SU,7
|
||||||
|
docopt-0.6.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||||
|
__pycache__/docopt.cpython-36.pyc,,
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
Wheel-Version: 1.0
|
||||||
|
Generator: bdist_wheel (0.29.0)
|
||||||
|
Root-Is-Purelib: true
|
||||||
|
Tag: py2-none-any
|
||||||
|
Tag: py3-none-any
|
||||||
|
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{"classifiers": ["Development Status :: 3 - Alpha", "Topic :: Utilities", "Programming Language :: Python :: 2.5", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.2", "Programming Language :: Python :: 3.3", "License :: OSI Approved :: MIT License"], "extensions": {"python.details": {"contacts": [{"email": "vladimir@keleshev.com", "name": "Vladimir Keleshev", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "http://docopt.org"}}}, "generator": "bdist_wheel (0.29.0)", "keywords": ["option", "arguments", "parsing", "optparse", "argparse", "getopt"], "license": "MIT", "metadata_version": "2.0", "name": "docopt", "summary": "Pythonic argument parser, that will make you smile", "version": "0.6.2"}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
docopt
|
||||||
579
venv/lib/python3.6/site-packages/docopt.py
Normal file
579
venv/lib/python3.6/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()
|
||||||
40
venv/lib/python3.6/site-packages/dotenv/__init__.py
Normal file
40
venv/lib/python3.6/site-packages/dotenv/__init__.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
from .main import load_dotenv, get_key, set_key, unset_key, find_dotenv, dotenv_values
|
||||||
|
|
||||||
|
|
||||||
|
def load_ipython_extension(ipython):
|
||||||
|
from .ipython import load_ipython_extension
|
||||||
|
load_ipython_extension(ipython)
|
||||||
|
|
||||||
|
|
||||||
|
def get_cli_string(path=None, action=None, key=None, value=None, quote=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']
|
||||||
98
venv/lib/python3.6/site-packages/dotenv/cli.py
Normal file
98
venv/lib/python3.6/site-packages/dotenv/cli.py
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
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, run_command
|
||||||
|
from .version import __version__
|
||||||
|
|
||||||
|
|
||||||
|
@click.group()
|
||||||
|
@click.option('-f', '--file', default=os.path.join(os.getcwd(), '.env'),
|
||||||
|
type=click.Path(exists=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.version_option(version=__version__)
|
||||||
|
@click.pass_context
|
||||||
|
def cli(ctx, file, quote):
|
||||||
|
'''This script is used to set, get or unset values from a .env file.'''
|
||||||
|
ctx.obj = {}
|
||||||
|
ctx.obj['FILE'] = file
|
||||||
|
ctx.obj['QUOTE'] = quote
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command()
|
||||||
|
@click.pass_context
|
||||||
|
def list(ctx):
|
||||||
|
'''Display all the stored key/value.'''
|
||||||
|
file = ctx.obj['FILE']
|
||||||
|
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, key, value):
|
||||||
|
'''Store the given key/value.'''
|
||||||
|
file = ctx.obj['FILE']
|
||||||
|
quote = ctx.obj['QUOTE']
|
||||||
|
success, key, value = set_key(file, key, value, quote)
|
||||||
|
if success:
|
||||||
|
click.echo('%s=%s' % (key, value))
|
||||||
|
else:
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command()
|
||||||
|
@click.pass_context
|
||||||
|
@click.argument('key', required=True)
|
||||||
|
def get(ctx, key):
|
||||||
|
'''Retrieve the value for the given key.'''
|
||||||
|
file = ctx.obj['FILE']
|
||||||
|
stored_value = get_key(file, key)
|
||||||
|
if stored_value:
|
||||||
|
click.echo('%s=%s' % (key, stored_value))
|
||||||
|
else:
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command()
|
||||||
|
@click.pass_context
|
||||||
|
@click.argument('key', required=True)
|
||||||
|
def unset(ctx, key):
|
||||||
|
'''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.argument('commandline', nargs=-1, type=click.UNPROCESSED)
|
||||||
|
def run(ctx, commandline):
|
||||||
|
"""Run command with environment variables present."""
|
||||||
|
file = ctx.obj['FILE']
|
||||||
|
dotenv_as_dict = dotenv_values(file)
|
||||||
|
if not commandline:
|
||||||
|
click.echo('No command given.')
|
||||||
|
exit(1)
|
||||||
|
ret = run_command(commandline, dotenv_as_dict)
|
||||||
|
exit(ret)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
cli()
|
||||||
9
venv/lib/python3.6/site-packages/dotenv/compat.py
Normal file
9
venv/lib/python3.6/site-packages/dotenv/compat.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import sys
|
||||||
|
try:
|
||||||
|
from StringIO import StringIO # noqa
|
||||||
|
except ImportError:
|
||||||
|
from io import StringIO # noqa
|
||||||
|
|
||||||
|
PY2 = sys.version_info[0] == 2
|
||||||
|
WIN = sys.platform.startswith('win')
|
||||||
|
text_type = unicode if PY2 else str # noqa
|
||||||
54
venv/lib/python3.6/site-packages/dotenv/environ.py
Normal file
54
venv/lib/python3.6/site-packages/dotenv/environ.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
class UndefinedValueError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Undefined(object):
|
||||||
|
"""Class to represent undefined type. """
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# Reference instance to represent undefined values
|
||||||
|
undefined = Undefined()
|
||||||
|
|
||||||
|
|
||||||
|
def _cast_boolean(value):
|
||||||
|
"""
|
||||||
|
Helper to convert config values to boolean as ConfigParser do.
|
||||||
|
"""
|
||||||
|
_BOOLEANS = {'1': True, 'yes': True, 'true': True, 'on': True,
|
||||||
|
'0': False, 'no': False, 'false': False, 'off': False, '': False}
|
||||||
|
value = str(value)
|
||||||
|
if value.lower() not in _BOOLEANS:
|
||||||
|
raise ValueError('Not a boolean: %s' % value)
|
||||||
|
|
||||||
|
return _BOOLEANS[value.lower()]
|
||||||
|
|
||||||
|
|
||||||
|
def getenv(option, default=undefined, cast=undefined):
|
||||||
|
"""
|
||||||
|
Return the value for option or default if defined.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# We can't avoid __contains__ because value may be empty.
|
||||||
|
if option in os.environ:
|
||||||
|
value = os.environ[option]
|
||||||
|
else:
|
||||||
|
if isinstance(default, Undefined):
|
||||||
|
raise UndefinedValueError('{} not found. Declare it as envvar or define a default value.'.format(option))
|
||||||
|
|
||||||
|
value = default
|
||||||
|
|
||||||
|
if isinstance(cast, Undefined):
|
||||||
|
return value
|
||||||
|
|
||||||
|
if cast is bool:
|
||||||
|
value = _cast_boolean(value)
|
||||||
|
elif cast is list:
|
||||||
|
value = [x for x in value.split(',') if x]
|
||||||
|
else:
|
||||||
|
value = cast(value)
|
||||||
|
|
||||||
|
return value
|
||||||
41
venv/lib/python3.6/site-packages/dotenv/ipython.py
Normal file
41
venv/lib/python3.6/site-packages/dotenv/ipython.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
from IPython.core.magic import Magics, line_magic, magics_class
|
||||||
|
from IPython.core.magic_arguments import (argument, magic_arguments,
|
||||||
|
parse_argstring)
|
||||||
|
|
||||||
|
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)
|
||||||
348
venv/lib/python3.6/site-packages/dotenv/main.py
Normal file
348
venv/lib/python3.6/site-packages/dotenv/main.py
Normal file
@ -0,0 +1,348 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import absolute_import, print_function, unicode_literals
|
||||||
|
|
||||||
|
import codecs
|
||||||
|
import io
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
from subprocess import Popen
|
||||||
|
import tempfile
|
||||||
|
import warnings
|
||||||
|
from collections import OrderedDict, namedtuple
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
from .compat import StringIO, PY2, WIN, text_type
|
||||||
|
|
||||||
|
__posix_variable = re.compile(r'\$\{[^\}]*\}')
|
||||||
|
|
||||||
|
_binding = re.compile(
|
||||||
|
r"""
|
||||||
|
(
|
||||||
|
\s* # leading whitespace
|
||||||
|
(?:export{0}+)? # export
|
||||||
|
|
||||||
|
( '[^']+' # single-quoted key
|
||||||
|
| [^=\#\s]+ # or unquoted key
|
||||||
|
)?
|
||||||
|
|
||||||
|
(?:
|
||||||
|
(?:{0}*={0}*) # equal sign
|
||||||
|
|
||||||
|
( '(?:\\'|[^'])*' # single-quoted value
|
||||||
|
| "(?:\\"|[^"])*" # or double-quoted value
|
||||||
|
| [^\#\r\n]* # or unquoted value
|
||||||
|
)
|
||||||
|
)?
|
||||||
|
|
||||||
|
\s* # trailing whitespace
|
||||||
|
(?:\#[^\r\n]*)? # comment
|
||||||
|
(?:\r|\n|\r\n)? # newline
|
||||||
|
)
|
||||||
|
""".format(r'[^\S\r\n]'),
|
||||||
|
re.MULTILINE | re.VERBOSE,
|
||||||
|
)
|
||||||
|
|
||||||
|
_escape_sequence = re.compile(r"\\[\\'\"abfnrtv]")
|
||||||
|
|
||||||
|
|
||||||
|
Binding = namedtuple('Binding', 'key value original')
|
||||||
|
|
||||||
|
|
||||||
|
def decode_escapes(string):
|
||||||
|
def decode_match(match):
|
||||||
|
return codecs.decode(match.group(0), 'unicode-escape')
|
||||||
|
|
||||||
|
return _escape_sequence.sub(decode_match, string)
|
||||||
|
|
||||||
|
|
||||||
|
def is_surrounded_by(string, char):
|
||||||
|
return (
|
||||||
|
len(string) > 1
|
||||||
|
and string[0] == string[-1] == char
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_binding(string, position):
|
||||||
|
match = _binding.match(string, position)
|
||||||
|
(matched, key, value) = match.groups()
|
||||||
|
if key is None or value is None:
|
||||||
|
key = None
|
||||||
|
value = None
|
||||||
|
else:
|
||||||
|
value_quoted = is_surrounded_by(value, "'") or is_surrounded_by(value, '"')
|
||||||
|
if value_quoted:
|
||||||
|
value = decode_escapes(value[1:-1])
|
||||||
|
else:
|
||||||
|
value = value.strip()
|
||||||
|
return (Binding(key=key, value=value, original=matched), match.end())
|
||||||
|
|
||||||
|
|
||||||
|
def parse_stream(stream):
|
||||||
|
string = stream.read()
|
||||||
|
position = 0
|
||||||
|
length = len(string)
|
||||||
|
while position < length:
|
||||||
|
(binding, position) = parse_binding(string, position)
|
||||||
|
yield binding
|
||||||
|
|
||||||
|
|
||||||
|
class DotEnv():
|
||||||
|
|
||||||
|
def __init__(self, dotenv_path, verbose=False):
|
||||||
|
self.dotenv_path = dotenv_path
|
||||||
|
self._dict = None
|
||||||
|
self.verbose = verbose
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def _get_stream(self):
|
||||||
|
if isinstance(self.dotenv_path, StringIO):
|
||||||
|
yield self.dotenv_path
|
||||||
|
elif os.path.isfile(self.dotenv_path):
|
||||||
|
with io.open(self.dotenv_path) as stream:
|
||||||
|
yield stream
|
||||||
|
else:
|
||||||
|
if self.verbose:
|
||||||
|
warnings.warn("File doesn't exist {}".format(self.dotenv_path))
|
||||||
|
yield StringIO('')
|
||||||
|
|
||||||
|
def dict(self):
|
||||||
|
"""Return dotenv as dict"""
|
||||||
|
if self._dict:
|
||||||
|
return self._dict
|
||||||
|
|
||||||
|
values = OrderedDict(self.parse())
|
||||||
|
self._dict = resolve_nested_variables(values)
|
||||||
|
return self._dict
|
||||||
|
|
||||||
|
def parse(self):
|
||||||
|
with self._get_stream() as stream:
|
||||||
|
for mapping in parse_stream(stream):
|
||||||
|
if mapping.key is not None and mapping.value is not None:
|
||||||
|
yield mapping.key, mapping.value
|
||||||
|
|
||||||
|
def set_as_environment_variables(self, override=False):
|
||||||
|
"""
|
||||||
|
Load the current dotenv as system environemt variable.
|
||||||
|
"""
|
||||||
|
for k, v in self.dict().items():
|
||||||
|
if k in os.environ and not override:
|
||||||
|
continue
|
||||||
|
# With Python2 on Windows, force environment variables to str to avoid
|
||||||
|
# "TypeError: environment can only contain strings" in Python's subprocess.py.
|
||||||
|
if PY2 and WIN:
|
||||||
|
if isinstance(k, text_type) or isinstance(v, text_type):
|
||||||
|
k = k.encode('ascii')
|
||||||
|
v = v.encode('ascii')
|
||||||
|
os.environ[k] = v
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get(self, key):
|
||||||
|
"""
|
||||||
|
"""
|
||||||
|
data = self.dict()
|
||||||
|
|
||||||
|
if key in data:
|
||||||
|
return data[key]
|
||||||
|
|
||||||
|
if self.verbose:
|
||||||
|
warnings.warn("key %s not found in %s." % (key, self.dotenv_path))
|
||||||
|
|
||||||
|
|
||||||
|
def get_key(dotenv_path, key_to_get):
|
||||||
|
"""
|
||||||
|
Gets the value of a given key from the given .env
|
||||||
|
|
||||||
|
If the .env path given doesn't exist, fails
|
||||||
|
"""
|
||||||
|
return DotEnv(dotenv_path, verbose=True).get(key_to_get)
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def rewrite(path):
|
||||||
|
try:
|
||||||
|
with tempfile.NamedTemporaryFile(mode="w+", delete=False) as dest:
|
||||||
|
with io.open(path) as source:
|
||||||
|
yield (source, dest)
|
||||||
|
except BaseException:
|
||||||
|
if os.path.isfile(dest.name):
|
||||||
|
os.unlink(dest.name)
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
shutil.move(dest.name, path)
|
||||||
|
|
||||||
|
|
||||||
|
def set_key(dotenv_path, key_to_set, value_to_set, quote_mode="always"):
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
"""
|
||||||
|
value_to_set = value_to_set.strip("'").strip('"')
|
||||||
|
if not os.path.exists(dotenv_path):
|
||||||
|
warnings.warn("can't write to %s - it doesn't exist." % dotenv_path)
|
||||||
|
return None, key_to_set, value_to_set
|
||||||
|
|
||||||
|
if " " in value_to_set:
|
||||||
|
quote_mode = "always"
|
||||||
|
|
||||||
|
line_template = '{}="{}"\n' if quote_mode == "always" else '{}={}\n'
|
||||||
|
line_out = line_template.format(key_to_set, value_to_set)
|
||||||
|
|
||||||
|
with rewrite(dotenv_path) as (source, dest):
|
||||||
|
replaced = False
|
||||||
|
for mapping in parse_stream(source):
|
||||||
|
if mapping.key == key_to_set:
|
||||||
|
dest.write(line_out)
|
||||||
|
replaced = True
|
||||||
|
else:
|
||||||
|
dest.write(mapping.original)
|
||||||
|
if not replaced:
|
||||||
|
dest.write(line_out)
|
||||||
|
|
||||||
|
return True, key_to_set, value_to_set
|
||||||
|
|
||||||
|
|
||||||
|
def unset_key(dotenv_path, key_to_unset, quote_mode="always"):
|
||||||
|
"""
|
||||||
|
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):
|
||||||
|
warnings.warn("can't delete from %s - it doesn't exist." % dotenv_path)
|
||||||
|
return None, key_to_unset
|
||||||
|
|
||||||
|
removed = False
|
||||||
|
with rewrite(dotenv_path) as (source, dest):
|
||||||
|
for mapping in parse_stream(source):
|
||||||
|
if mapping.key == key_to_unset:
|
||||||
|
removed = True
|
||||||
|
else:
|
||||||
|
dest.write(mapping.original)
|
||||||
|
|
||||||
|
if not removed:
|
||||||
|
warnings.warn("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_nested_variables(values):
|
||||||
|
def _replacement(name):
|
||||||
|
"""
|
||||||
|
get appropriate value for a variable name.
|
||||||
|
first search in environ, if not found,
|
||||||
|
then look into the dotenv variables
|
||||||
|
"""
|
||||||
|
ret = os.getenv(name, new_values.get(name, ""))
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def _re_sub_callback(match_object):
|
||||||
|
"""
|
||||||
|
From a match object gets the variable name and returns
|
||||||
|
the correct replacement
|
||||||
|
"""
|
||||||
|
return _replacement(match_object.group()[2:-1])
|
||||||
|
|
||||||
|
new_values = {}
|
||||||
|
|
||||||
|
for k, v in values.items():
|
||||||
|
new_values[k] = __posix_variable.sub(_re_sub_callback, v)
|
||||||
|
|
||||||
|
return new_values
|
||||||
|
|
||||||
|
|
||||||
|
def _walk_to_root(path):
|
||||||
|
"""
|
||||||
|
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='.env', raise_error_if_not_found=False, usecwd=False):
|
||||||
|
"""
|
||||||
|
Search in increasingly higher folders for the given file
|
||||||
|
|
||||||
|
Returns path to the file if found, or an empty string otherwise
|
||||||
|
"""
|
||||||
|
if usecwd or '__file__' not in globals():
|
||||||
|
# should work without __file__, e.g. in REPL or IPython notebook
|
||||||
|
path = os.getcwd()
|
||||||
|
else:
|
||||||
|
# will work for .py files
|
||||||
|
frame = sys._getframe()
|
||||||
|
# find first frame that is outside of this file
|
||||||
|
while frame.f_code.co_filename == __file__:
|
||||||
|
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=None, stream=None, verbose=False, override=False):
|
||||||
|
f = dotenv_path or stream or find_dotenv()
|
||||||
|
return DotEnv(f, verbose=verbose).set_as_environment_variables(override=override)
|
||||||
|
|
||||||
|
|
||||||
|
def dotenv_values(dotenv_path=None, stream=None, verbose=False):
|
||||||
|
f = dotenv_path or stream or find_dotenv()
|
||||||
|
return DotEnv(f, verbose=verbose).dict()
|
||||||
|
|
||||||
|
|
||||||
|
def run_command(command, env):
|
||||||
|
"""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
|
||||||
1
venv/lib/python3.6/site-packages/dotenv/version.py
Normal file
1
venv/lib/python3.6/site-packages/dotenv/version.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
__version__ = "0.10.1"
|
||||||
2
venv/lib/python3.6/site-packages/easy-install.pth
Normal file
2
venv/lib/python3.6/site-packages/easy-install.pth
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
./setuptools-39.1.0-py3.6.egg
|
||||||
|
./pip-10.0.1-py3.6.egg
|
||||||
@ -0,0 +1,69 @@
|
|||||||
|
Metadata-Version: 2.1
|
||||||
|
Name: pip
|
||||||
|
Version: 10.0.1
|
||||||
|
Summary: The PyPA recommended tool for installing Python packages.
|
||||||
|
Home-page: https://pip.pypa.io/
|
||||||
|
Author: The pip developers
|
||||||
|
Author-email: python-virtualenv@groups.google.com
|
||||||
|
License: MIT
|
||||||
|
Description: pip
|
||||||
|
===
|
||||||
|
|
||||||
|
The `PyPA recommended`_ tool for installing Python packages.
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/v/pip.svg
|
||||||
|
:target: https://pypi.org/project/pip/
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/travis/pypa/pip/master.svg
|
||||||
|
:target: http://travis-ci.org/pypa/pip
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/appveyor/ci/pypa/pip.svg
|
||||||
|
:target: https://ci.appveyor.com/project/pypa/pip/history
|
||||||
|
|
||||||
|
.. image:: https://readthedocs.org/projects/pip/badge/?version=latest
|
||||||
|
:target: https://pip.pypa.io/en/latest
|
||||||
|
|
||||||
|
* `Installation`_
|
||||||
|
* `Documentation`_
|
||||||
|
* `Changelog`_
|
||||||
|
* `GitHub Page`_
|
||||||
|
* `Issue Tracking`_
|
||||||
|
* `User mailing list`_
|
||||||
|
* `Dev mailing list`_
|
||||||
|
* User IRC: #pypa on Freenode.
|
||||||
|
* Dev IRC: #pypa-dev on Freenode.
|
||||||
|
|
||||||
|
Code of Conduct
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Everyone interacting in the pip project's codebases, issue trackers, chat
|
||||||
|
rooms and mailing lists is expected to follow the `PyPA Code of Conduct`_.
|
||||||
|
|
||||||
|
.. _PyPA recommended: https://packaging.python.org/en/latest/current/
|
||||||
|
.. _Installation: https://pip.pypa.io/en/stable/installing.html
|
||||||
|
.. _Documentation: https://pip.pypa.io/en/stable/
|
||||||
|
.. _Changelog: https://pip.pypa.io/en/stable/news.html
|
||||||
|
.. _GitHub Page: https://github.com/pypa/pip
|
||||||
|
.. _Issue Tracking: https://github.com/pypa/pip/issues
|
||||||
|
.. _User mailing list: http://groups.google.com/group/python-virtualenv
|
||||||
|
.. _Dev mailing list: http://groups.google.com/group/pypa-dev
|
||||||
|
.. _PyPA Code of Conduct: https://www.pypa.io/en/latest/code-of-conduct/
|
||||||
|
|
||||||
|
Keywords: easy_install distutils setuptools egg virtualenv
|
||||||
|
Platform: UNKNOWN
|
||||||
|
Classifier: Development Status :: 5 - Production/Stable
|
||||||
|
Classifier: Intended Audience :: Developers
|
||||||
|
Classifier: License :: OSI Approved :: MIT License
|
||||||
|
Classifier: Topic :: Software Development :: Build Tools
|
||||||
|
Classifier: Programming Language :: Python
|
||||||
|
Classifier: Programming Language :: Python :: 2
|
||||||
|
Classifier: Programming Language :: Python :: 2.7
|
||||||
|
Classifier: Programming Language :: Python :: 3
|
||||||
|
Classifier: Programming Language :: Python :: 3.3
|
||||||
|
Classifier: Programming Language :: Python :: 3.4
|
||||||
|
Classifier: Programming Language :: Python :: 3.5
|
||||||
|
Classifier: Programming Language :: Python :: 3.6
|
||||||
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
||||||
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||||
|
Requires-Python: >=2.7,!=3.0.*,!=3.1.*,!=3.2.*
|
||||||
|
Provides-Extra: testing
|
||||||
@ -0,0 +1,347 @@
|
|||||||
|
AUTHORS.txt
|
||||||
|
LICENSE.txt
|
||||||
|
MANIFEST.in
|
||||||
|
NEWS.rst
|
||||||
|
README.rst
|
||||||
|
pyproject.toml
|
||||||
|
setup.cfg
|
||||||
|
setup.py
|
||||||
|
docs/Makefile
|
||||||
|
docs/__init__.py
|
||||||
|
docs/conf.py
|
||||||
|
docs/configuration.rst
|
||||||
|
docs/cookbook.rst
|
||||||
|
docs/development.rst
|
||||||
|
docs/docutils.conf
|
||||||
|
docs/index.rst
|
||||||
|
docs/installing.rst
|
||||||
|
docs/logic.rst
|
||||||
|
docs/make.bat
|
||||||
|
docs/news.rst
|
||||||
|
docs/pipext.py
|
||||||
|
docs/quickstart.rst
|
||||||
|
docs/usage.rst
|
||||||
|
docs/user_guide.rst
|
||||||
|
docs/man/pip.rst
|
||||||
|
docs/man/commands/check.rst
|
||||||
|
docs/man/commands/config.rst
|
||||||
|
docs/man/commands/download.rst
|
||||||
|
docs/man/commands/freeze.rst
|
||||||
|
docs/man/commands/hash.rst
|
||||||
|
docs/man/commands/help.rst
|
||||||
|
docs/man/commands/install.rst
|
||||||
|
docs/man/commands/list.rst
|
||||||
|
docs/man/commands/search.rst
|
||||||
|
docs/man/commands/show.rst
|
||||||
|
docs/man/commands/uninstall.rst
|
||||||
|
docs/man/commands/wheel.rst
|
||||||
|
docs/reference/index.rst
|
||||||
|
docs/reference/pip.rst
|
||||||
|
docs/reference/pip_check.rst
|
||||||
|
docs/reference/pip_config.rst
|
||||||
|
docs/reference/pip_download.rst
|
||||||
|
docs/reference/pip_freeze.rst
|
||||||
|
docs/reference/pip_hash.rst
|
||||||
|
docs/reference/pip_install.rst
|
||||||
|
docs/reference/pip_list.rst
|
||||||
|
docs/reference/pip_search.rst
|
||||||
|
docs/reference/pip_show.rst
|
||||||
|
docs/reference/pip_uninstall.rst
|
||||||
|
docs/reference/pip_wheel.rst
|
||||||
|
src/pip/__init__.py
|
||||||
|
src/pip/__main__.py
|
||||||
|
src/pip.egg-info/PKG-INFO
|
||||||
|
src/pip.egg-info/SOURCES.txt
|
||||||
|
src/pip.egg-info/dependency_links.txt
|
||||||
|
src/pip.egg-info/entry_points.txt
|
||||||
|
src/pip.egg-info/not-zip-safe
|
||||||
|
src/pip.egg-info/requires.txt
|
||||||
|
src/pip.egg-info/top_level.txt
|
||||||
|
src/pip/_internal/__init__.py
|
||||||
|
src/pip/_internal/basecommand.py
|
||||||
|
src/pip/_internal/baseparser.py
|
||||||
|
src/pip/_internal/build_env.py
|
||||||
|
src/pip/_internal/cache.py
|
||||||
|
src/pip/_internal/cmdoptions.py
|
||||||
|
src/pip/_internal/compat.py
|
||||||
|
src/pip/_internal/configuration.py
|
||||||
|
src/pip/_internal/download.py
|
||||||
|
src/pip/_internal/exceptions.py
|
||||||
|
src/pip/_internal/index.py
|
||||||
|
src/pip/_internal/locations.py
|
||||||
|
src/pip/_internal/pep425tags.py
|
||||||
|
src/pip/_internal/resolve.py
|
||||||
|
src/pip/_internal/status_codes.py
|
||||||
|
src/pip/_internal/wheel.py
|
||||||
|
src/pip/_internal/commands/__init__.py
|
||||||
|
src/pip/_internal/commands/check.py
|
||||||
|
src/pip/_internal/commands/completion.py
|
||||||
|
src/pip/_internal/commands/configuration.py
|
||||||
|
src/pip/_internal/commands/download.py
|
||||||
|
src/pip/_internal/commands/freeze.py
|
||||||
|
src/pip/_internal/commands/hash.py
|
||||||
|
src/pip/_internal/commands/help.py
|
||||||
|
src/pip/_internal/commands/install.py
|
||||||
|
src/pip/_internal/commands/list.py
|
||||||
|
src/pip/_internal/commands/search.py
|
||||||
|
src/pip/_internal/commands/show.py
|
||||||
|
src/pip/_internal/commands/uninstall.py
|
||||||
|
src/pip/_internal/commands/wheel.py
|
||||||
|
src/pip/_internal/models/__init__.py
|
||||||
|
src/pip/_internal/models/index.py
|
||||||
|
src/pip/_internal/operations/__init__.py
|
||||||
|
src/pip/_internal/operations/check.py
|
||||||
|
src/pip/_internal/operations/freeze.py
|
||||||
|
src/pip/_internal/operations/prepare.py
|
||||||
|
src/pip/_internal/req/__init__.py
|
||||||
|
src/pip/_internal/req/req_file.py
|
||||||
|
src/pip/_internal/req/req_install.py
|
||||||
|
src/pip/_internal/req/req_set.py
|
||||||
|
src/pip/_internal/req/req_uninstall.py
|
||||||
|
src/pip/_internal/utils/__init__.py
|
||||||
|
src/pip/_internal/utils/appdirs.py
|
||||||
|
src/pip/_internal/utils/deprecation.py
|
||||||
|
src/pip/_internal/utils/encoding.py
|
||||||
|
src/pip/_internal/utils/filesystem.py
|
||||||
|
src/pip/_internal/utils/glibc.py
|
||||||
|
src/pip/_internal/utils/hashes.py
|
||||||
|
src/pip/_internal/utils/logging.py
|
||||||
|
src/pip/_internal/utils/misc.py
|
||||||
|
src/pip/_internal/utils/outdated.py
|
||||||
|
src/pip/_internal/utils/packaging.py
|
||||||
|
src/pip/_internal/utils/setuptools_build.py
|
||||||
|
src/pip/_internal/utils/temp_dir.py
|
||||||
|
src/pip/_internal/utils/typing.py
|
||||||
|
src/pip/_internal/utils/ui.py
|
||||||
|
src/pip/_internal/vcs/__init__.py
|
||||||
|
src/pip/_internal/vcs/bazaar.py
|
||||||
|
src/pip/_internal/vcs/git.py
|
||||||
|
src/pip/_internal/vcs/mercurial.py
|
||||||
|
src/pip/_internal/vcs/subversion.py
|
||||||
|
src/pip/_vendor/README.rst
|
||||||
|
src/pip/_vendor/__init__.py
|
||||||
|
src/pip/_vendor/appdirs.py
|
||||||
|
src/pip/_vendor/distro.py
|
||||||
|
src/pip/_vendor/ipaddress.py
|
||||||
|
src/pip/_vendor/pyparsing.py
|
||||||
|
src/pip/_vendor/retrying.py
|
||||||
|
src/pip/_vendor/six.py
|
||||||
|
src/pip/_vendor/vendor.txt
|
||||||
|
src/pip/_vendor/cachecontrol/__init__.py
|
||||||
|
src/pip/_vendor/cachecontrol/_cmd.py
|
||||||
|
src/pip/_vendor/cachecontrol/adapter.py
|
||||||
|
src/pip/_vendor/cachecontrol/cache.py
|
||||||
|
src/pip/_vendor/cachecontrol/compat.py
|
||||||
|
src/pip/_vendor/cachecontrol/controller.py
|
||||||
|
src/pip/_vendor/cachecontrol/filewrapper.py
|
||||||
|
src/pip/_vendor/cachecontrol/heuristics.py
|
||||||
|
src/pip/_vendor/cachecontrol/serialize.py
|
||||||
|
src/pip/_vendor/cachecontrol/wrapper.py
|
||||||
|
src/pip/_vendor/cachecontrol/caches/__init__.py
|
||||||
|
src/pip/_vendor/cachecontrol/caches/file_cache.py
|
||||||
|
src/pip/_vendor/cachecontrol/caches/redis_cache.py
|
||||||
|
src/pip/_vendor/certifi/__init__.py
|
||||||
|
src/pip/_vendor/certifi/__main__.py
|
||||||
|
src/pip/_vendor/certifi/cacert.pem
|
||||||
|
src/pip/_vendor/certifi/core.py
|
||||||
|
src/pip/_vendor/chardet/__init__.py
|
||||||
|
src/pip/_vendor/chardet/big5freq.py
|
||||||
|
src/pip/_vendor/chardet/big5prober.py
|
||||||
|
src/pip/_vendor/chardet/chardistribution.py
|
||||||
|
src/pip/_vendor/chardet/charsetgroupprober.py
|
||||||
|
src/pip/_vendor/chardet/charsetprober.py
|
||||||
|
src/pip/_vendor/chardet/codingstatemachine.py
|
||||||
|
src/pip/_vendor/chardet/compat.py
|
||||||
|
src/pip/_vendor/chardet/cp949prober.py
|
||||||
|
src/pip/_vendor/chardet/enums.py
|
||||||
|
src/pip/_vendor/chardet/escprober.py
|
||||||
|
src/pip/_vendor/chardet/escsm.py
|
||||||
|
src/pip/_vendor/chardet/eucjpprober.py
|
||||||
|
src/pip/_vendor/chardet/euckrfreq.py
|
||||||
|
src/pip/_vendor/chardet/euckrprober.py
|
||||||
|
src/pip/_vendor/chardet/euctwfreq.py
|
||||||
|
src/pip/_vendor/chardet/euctwprober.py
|
||||||
|
src/pip/_vendor/chardet/gb2312freq.py
|
||||||
|
src/pip/_vendor/chardet/gb2312prober.py
|
||||||
|
src/pip/_vendor/chardet/hebrewprober.py
|
||||||
|
src/pip/_vendor/chardet/jisfreq.py
|
||||||
|
src/pip/_vendor/chardet/jpcntx.py
|
||||||
|
src/pip/_vendor/chardet/langbulgarianmodel.py
|
||||||
|
src/pip/_vendor/chardet/langcyrillicmodel.py
|
||||||
|
src/pip/_vendor/chardet/langgreekmodel.py
|
||||||
|
src/pip/_vendor/chardet/langhebrewmodel.py
|
||||||
|
src/pip/_vendor/chardet/langhungarianmodel.py
|
||||||
|
src/pip/_vendor/chardet/langthaimodel.py
|
||||||
|
src/pip/_vendor/chardet/langturkishmodel.py
|
||||||
|
src/pip/_vendor/chardet/latin1prober.py
|
||||||
|
src/pip/_vendor/chardet/mbcharsetprober.py
|
||||||
|
src/pip/_vendor/chardet/mbcsgroupprober.py
|
||||||
|
src/pip/_vendor/chardet/mbcssm.py
|
||||||
|
src/pip/_vendor/chardet/sbcharsetprober.py
|
||||||
|
src/pip/_vendor/chardet/sbcsgroupprober.py
|
||||||
|
src/pip/_vendor/chardet/sjisprober.py
|
||||||
|
src/pip/_vendor/chardet/universaldetector.py
|
||||||
|
src/pip/_vendor/chardet/utf8prober.py
|
||||||
|
src/pip/_vendor/chardet/version.py
|
||||||
|
src/pip/_vendor/chardet/cli/__init__.py
|
||||||
|
src/pip/_vendor/chardet/cli/chardetect.py
|
||||||
|
src/pip/_vendor/colorama/__init__.py
|
||||||
|
src/pip/_vendor/colorama/ansi.py
|
||||||
|
src/pip/_vendor/colorama/ansitowin32.py
|
||||||
|
src/pip/_vendor/colorama/initialise.py
|
||||||
|
src/pip/_vendor/colorama/win32.py
|
||||||
|
src/pip/_vendor/colorama/winterm.py
|
||||||
|
src/pip/_vendor/distlib/__init__.py
|
||||||
|
src/pip/_vendor/distlib/compat.py
|
||||||
|
src/pip/_vendor/distlib/database.py
|
||||||
|
src/pip/_vendor/distlib/index.py
|
||||||
|
src/pip/_vendor/distlib/locators.py
|
||||||
|
src/pip/_vendor/distlib/manifest.py
|
||||||
|
src/pip/_vendor/distlib/markers.py
|
||||||
|
src/pip/_vendor/distlib/metadata.py
|
||||||
|
src/pip/_vendor/distlib/resources.py
|
||||||
|
src/pip/_vendor/distlib/scripts.py
|
||||||
|
src/pip/_vendor/distlib/t32.exe
|
||||||
|
src/pip/_vendor/distlib/t64.exe
|
||||||
|
src/pip/_vendor/distlib/util.py
|
||||||
|
src/pip/_vendor/distlib/version.py
|
||||||
|
src/pip/_vendor/distlib/w32.exe
|
||||||
|
src/pip/_vendor/distlib/w64.exe
|
||||||
|
src/pip/_vendor/distlib/wheel.py
|
||||||
|
src/pip/_vendor/distlib/_backport/__init__.py
|
||||||
|
src/pip/_vendor/distlib/_backport/misc.py
|
||||||
|
src/pip/_vendor/distlib/_backport/shutil.py
|
||||||
|
src/pip/_vendor/distlib/_backport/sysconfig.cfg
|
||||||
|
src/pip/_vendor/distlib/_backport/sysconfig.py
|
||||||
|
src/pip/_vendor/distlib/_backport/tarfile.py
|
||||||
|
src/pip/_vendor/html5lib/__init__.py
|
||||||
|
src/pip/_vendor/html5lib/_ihatexml.py
|
||||||
|
src/pip/_vendor/html5lib/_inputstream.py
|
||||||
|
src/pip/_vendor/html5lib/_tokenizer.py
|
||||||
|
src/pip/_vendor/html5lib/_utils.py
|
||||||
|
src/pip/_vendor/html5lib/constants.py
|
||||||
|
src/pip/_vendor/html5lib/html5parser.py
|
||||||
|
src/pip/_vendor/html5lib/serializer.py
|
||||||
|
src/pip/_vendor/html5lib/_trie/__init__.py
|
||||||
|
src/pip/_vendor/html5lib/_trie/_base.py
|
||||||
|
src/pip/_vendor/html5lib/_trie/datrie.py
|
||||||
|
src/pip/_vendor/html5lib/_trie/py.py
|
||||||
|
src/pip/_vendor/html5lib/filters/__init__.py
|
||||||
|
src/pip/_vendor/html5lib/filters/alphabeticalattributes.py
|
||||||
|
src/pip/_vendor/html5lib/filters/base.py
|
||||||
|
src/pip/_vendor/html5lib/filters/inject_meta_charset.py
|
||||||
|
src/pip/_vendor/html5lib/filters/lint.py
|
||||||
|
src/pip/_vendor/html5lib/filters/optionaltags.py
|
||||||
|
src/pip/_vendor/html5lib/filters/sanitizer.py
|
||||||
|
src/pip/_vendor/html5lib/filters/whitespace.py
|
||||||
|
src/pip/_vendor/html5lib/treeadapters/__init__.py
|
||||||
|
src/pip/_vendor/html5lib/treeadapters/genshi.py
|
||||||
|
src/pip/_vendor/html5lib/treeadapters/sax.py
|
||||||
|
src/pip/_vendor/html5lib/treebuilders/__init__.py
|
||||||
|
src/pip/_vendor/html5lib/treebuilders/base.py
|
||||||
|
src/pip/_vendor/html5lib/treebuilders/dom.py
|
||||||
|
src/pip/_vendor/html5lib/treebuilders/etree.py
|
||||||
|
src/pip/_vendor/html5lib/treebuilders/etree_lxml.py
|
||||||
|
src/pip/_vendor/html5lib/treewalkers/__init__.py
|
||||||
|
src/pip/_vendor/html5lib/treewalkers/base.py
|
||||||
|
src/pip/_vendor/html5lib/treewalkers/dom.py
|
||||||
|
src/pip/_vendor/html5lib/treewalkers/etree.py
|
||||||
|
src/pip/_vendor/html5lib/treewalkers/etree_lxml.py
|
||||||
|
src/pip/_vendor/html5lib/treewalkers/genshi.py
|
||||||
|
src/pip/_vendor/idna/__init__.py
|
||||||
|
src/pip/_vendor/idna/codec.py
|
||||||
|
src/pip/_vendor/idna/compat.py
|
||||||
|
src/pip/_vendor/idna/core.py
|
||||||
|
src/pip/_vendor/idna/idnadata.py
|
||||||
|
src/pip/_vendor/idna/intranges.py
|
||||||
|
src/pip/_vendor/idna/package_data.py
|
||||||
|
src/pip/_vendor/idna/uts46data.py
|
||||||
|
src/pip/_vendor/lockfile/__init__.py
|
||||||
|
src/pip/_vendor/lockfile/linklockfile.py
|
||||||
|
src/pip/_vendor/lockfile/mkdirlockfile.py
|
||||||
|
src/pip/_vendor/lockfile/pidlockfile.py
|
||||||
|
src/pip/_vendor/lockfile/sqlitelockfile.py
|
||||||
|
src/pip/_vendor/lockfile/symlinklockfile.py
|
||||||
|
src/pip/_vendor/msgpack/__init__.py
|
||||||
|
src/pip/_vendor/msgpack/_version.py
|
||||||
|
src/pip/_vendor/msgpack/exceptions.py
|
||||||
|
src/pip/_vendor/msgpack/fallback.py
|
||||||
|
src/pip/_vendor/packaging/__about__.py
|
||||||
|
src/pip/_vendor/packaging/__init__.py
|
||||||
|
src/pip/_vendor/packaging/_compat.py
|
||||||
|
src/pip/_vendor/packaging/_structures.py
|
||||||
|
src/pip/_vendor/packaging/markers.py
|
||||||
|
src/pip/_vendor/packaging/requirements.py
|
||||||
|
src/pip/_vendor/packaging/specifiers.py
|
||||||
|
src/pip/_vendor/packaging/utils.py
|
||||||
|
src/pip/_vendor/packaging/version.py
|
||||||
|
src/pip/_vendor/pkg_resources/__init__.py
|
||||||
|
src/pip/_vendor/pkg_resources/py31compat.py
|
||||||
|
src/pip/_vendor/progress/__init__.py
|
||||||
|
src/pip/_vendor/progress/bar.py
|
||||||
|
src/pip/_vendor/progress/counter.py
|
||||||
|
src/pip/_vendor/progress/helpers.py
|
||||||
|
src/pip/_vendor/progress/spinner.py
|
||||||
|
src/pip/_vendor/pytoml/__init__.py
|
||||||
|
src/pip/_vendor/pytoml/core.py
|
||||||
|
src/pip/_vendor/pytoml/parser.py
|
||||||
|
src/pip/_vendor/pytoml/writer.py
|
||||||
|
src/pip/_vendor/requests/__init__.py
|
||||||
|
src/pip/_vendor/requests/__version__.py
|
||||||
|
src/pip/_vendor/requests/_internal_utils.py
|
||||||
|
src/pip/_vendor/requests/adapters.py
|
||||||
|
src/pip/_vendor/requests/api.py
|
||||||
|
src/pip/_vendor/requests/auth.py
|
||||||
|
src/pip/_vendor/requests/certs.py
|
||||||
|
src/pip/_vendor/requests/compat.py
|
||||||
|
src/pip/_vendor/requests/cookies.py
|
||||||
|
src/pip/_vendor/requests/exceptions.py
|
||||||
|
src/pip/_vendor/requests/help.py
|
||||||
|
src/pip/_vendor/requests/hooks.py
|
||||||
|
src/pip/_vendor/requests/models.py
|
||||||
|
src/pip/_vendor/requests/packages.py
|
||||||
|
src/pip/_vendor/requests/sessions.py
|
||||||
|
src/pip/_vendor/requests/status_codes.py
|
||||||
|
src/pip/_vendor/requests/structures.py
|
||||||
|
src/pip/_vendor/requests/utils.py
|
||||||
|
src/pip/_vendor/urllib3/__init__.py
|
||||||
|
src/pip/_vendor/urllib3/_collections.py
|
||||||
|
src/pip/_vendor/urllib3/connection.py
|
||||||
|
src/pip/_vendor/urllib3/connectionpool.py
|
||||||
|
src/pip/_vendor/urllib3/exceptions.py
|
||||||
|
src/pip/_vendor/urllib3/fields.py
|
||||||
|
src/pip/_vendor/urllib3/filepost.py
|
||||||
|
src/pip/_vendor/urllib3/poolmanager.py
|
||||||
|
src/pip/_vendor/urllib3/request.py
|
||||||
|
src/pip/_vendor/urllib3/response.py
|
||||||
|
src/pip/_vendor/urllib3/contrib/__init__.py
|
||||||
|
src/pip/_vendor/urllib3/contrib/appengine.py
|
||||||
|
src/pip/_vendor/urllib3/contrib/ntlmpool.py
|
||||||
|
src/pip/_vendor/urllib3/contrib/pyopenssl.py
|
||||||
|
src/pip/_vendor/urllib3/contrib/securetransport.py
|
||||||
|
src/pip/_vendor/urllib3/contrib/socks.py
|
||||||
|
src/pip/_vendor/urllib3/contrib/_securetransport/__init__.py
|
||||||
|
src/pip/_vendor/urllib3/contrib/_securetransport/bindings.py
|
||||||
|
src/pip/_vendor/urllib3/contrib/_securetransport/low_level.py
|
||||||
|
src/pip/_vendor/urllib3/packages/__init__.py
|
||||||
|
src/pip/_vendor/urllib3/packages/ordered_dict.py
|
||||||
|
src/pip/_vendor/urllib3/packages/six.py
|
||||||
|
src/pip/_vendor/urllib3/packages/backports/__init__.py
|
||||||
|
src/pip/_vendor/urllib3/packages/backports/makefile.py
|
||||||
|
src/pip/_vendor/urllib3/packages/ssl_match_hostname/__init__.py
|
||||||
|
src/pip/_vendor/urllib3/packages/ssl_match_hostname/_implementation.py
|
||||||
|
src/pip/_vendor/urllib3/util/__init__.py
|
||||||
|
src/pip/_vendor/urllib3/util/connection.py
|
||||||
|
src/pip/_vendor/urllib3/util/request.py
|
||||||
|
src/pip/_vendor/urllib3/util/response.py
|
||||||
|
src/pip/_vendor/urllib3/util/retry.py
|
||||||
|
src/pip/_vendor/urllib3/util/selectors.py
|
||||||
|
src/pip/_vendor/urllib3/util/ssl_.py
|
||||||
|
src/pip/_vendor/urllib3/util/timeout.py
|
||||||
|
src/pip/_vendor/urllib3/util/url.py
|
||||||
|
src/pip/_vendor/urllib3/util/wait.py
|
||||||
|
src/pip/_vendor/webencodings/__init__.py
|
||||||
|
src/pip/_vendor/webencodings/labels.py
|
||||||
|
src/pip/_vendor/webencodings/mklabels.py
|
||||||
|
src/pip/_vendor/webencodings/tests.py
|
||||||
|
src/pip/_vendor/webencodings/x_user_defined.py
|
||||||
@ -0,0 +1 @@
|
|||||||
|
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
[console_scripts]
|
||||||
|
pip = pip._internal:main
|
||||||
|
pip3 = pip._internal:main
|
||||||
|
pip3.6 = pip._internal:main
|
||||||
|
|
||||||
@ -0,0 +1 @@
|
|||||||
|
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
[testing]
|
||||||
|
pytest
|
||||||
|
mock
|
||||||
|
pretend
|
||||||
|
scripttest>=1.3
|
||||||
|
virtualenv>=1.10
|
||||||
|
freezegun
|
||||||
@ -0,0 +1 @@
|
|||||||
|
pip
|
||||||
@ -0,0 +1 @@
|
|||||||
|
__version__ = "10.0.1"
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# If we are running from a wheel, add the wheel to sys.path
|
||||||
|
# This allows the usage python pip-*.whl/pip install pip-*.whl
|
||||||
|
if __package__ == '':
|
||||||
|
# __file__ is pip-*.whl/pip/__main__.py
|
||||||
|
# first dirname call strips of '/__main__.py', second strips off '/pip'
|
||||||
|
# Resulting path is the name of the wheel itself
|
||||||
|
# Add that to sys.path so we can import pip
|
||||||
|
path = os.path.dirname(os.path.dirname(__file__))
|
||||||
|
sys.path.insert(0, path)
|
||||||
|
|
||||||
|
from pip._internal import main as _main # noqa
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(_main())
|
||||||
@ -0,0 +1,246 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import locale
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import optparse
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# 2016-06-17 barry@debian.org: urllib3 1.14 added optional support for socks,
|
||||||
|
# but if invoked (i.e. imported), it will issue a warning to stderr if socks
|
||||||
|
# isn't available. requests unconditionally imports urllib3's socks contrib
|
||||||
|
# module, triggering this warning. The warning breaks DEP-8 tests (because of
|
||||||
|
# the stderr output) and is just plain annoying in normal usage. I don't want
|
||||||
|
# to add socks as yet another dependency for pip, nor do I want to allow-stder
|
||||||
|
# in the DEP-8 tests, so just suppress the warning. pdb tells me this has to
|
||||||
|
# be done before the import of pip.vcs.
|
||||||
|
from pip._vendor.urllib3.exceptions import DependencyWarning
|
||||||
|
warnings.filterwarnings("ignore", category=DependencyWarning) # noqa
|
||||||
|
|
||||||
|
# We want to inject the use of SecureTransport as early as possible so that any
|
||||||
|
# references or sessions or what have you are ensured to have it, however we
|
||||||
|
# only want to do this in the case that we're running on macOS and the linked
|
||||||
|
# OpenSSL is too old to handle TLSv1.2
|
||||||
|
try:
|
||||||
|
import ssl
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
# Checks for OpenSSL 1.0.1 on MacOS
|
||||||
|
if sys.platform == "darwin" and ssl.OPENSSL_VERSION_NUMBER < 0x1000100f:
|
||||||
|
try:
|
||||||
|
from pip._vendor.urllib3.contrib import securetransport
|
||||||
|
except (ImportError, OSError):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
securetransport.inject_into_urllib3()
|
||||||
|
|
||||||
|
from pip import __version__
|
||||||
|
from pip._internal import cmdoptions
|
||||||
|
from pip._internal.exceptions import CommandError, PipError
|
||||||
|
from pip._internal.utils.misc import get_installed_distributions, get_prog
|
||||||
|
from pip._internal.utils import deprecation
|
||||||
|
from pip._internal.vcs import git, mercurial, subversion, bazaar # noqa
|
||||||
|
from pip._internal.baseparser import (
|
||||||
|
ConfigOptionParser, UpdatingDefaultsHelpFormatter,
|
||||||
|
)
|
||||||
|
from pip._internal.commands import get_summaries, get_similar_commands
|
||||||
|
from pip._internal.commands import commands_dict
|
||||||
|
from pip._vendor.urllib3.exceptions import InsecureRequestWarning
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Hide the InsecureRequestWarning from urllib3
|
||||||
|
warnings.filterwarnings("ignore", category=InsecureRequestWarning)
|
||||||
|
|
||||||
|
|
||||||
|
def autocomplete():
|
||||||
|
"""Command and option completion for the main option parser (and options)
|
||||||
|
and its subcommands (and options).
|
||||||
|
|
||||||
|
Enable by sourcing one of the completion shell scripts (bash, zsh or fish).
|
||||||
|
"""
|
||||||
|
# Don't complete if user hasn't sourced bash_completion file.
|
||||||
|
if 'PIP_AUTO_COMPLETE' not in os.environ:
|
||||||
|
return
|
||||||
|
cwords = os.environ['COMP_WORDS'].split()[1:]
|
||||||
|
cword = int(os.environ['COMP_CWORD'])
|
||||||
|
try:
|
||||||
|
current = cwords[cword - 1]
|
||||||
|
except IndexError:
|
||||||
|
current = ''
|
||||||
|
|
||||||
|
subcommands = [cmd for cmd, summary in get_summaries()]
|
||||||
|
options = []
|
||||||
|
# subcommand
|
||||||
|
try:
|
||||||
|
subcommand_name = [w for w in cwords if w in subcommands][0]
|
||||||
|
except IndexError:
|
||||||
|
subcommand_name = None
|
||||||
|
|
||||||
|
parser = create_main_parser()
|
||||||
|
# subcommand options
|
||||||
|
if subcommand_name:
|
||||||
|
# special case: 'help' subcommand has no options
|
||||||
|
if subcommand_name == 'help':
|
||||||
|
sys.exit(1)
|
||||||
|
# special case: list locally installed dists for show and uninstall
|
||||||
|
should_list_installed = (
|
||||||
|
subcommand_name in ['show', 'uninstall'] and
|
||||||
|
not current.startswith('-')
|
||||||
|
)
|
||||||
|
if should_list_installed:
|
||||||
|
installed = []
|
||||||
|
lc = current.lower()
|
||||||
|
for dist in get_installed_distributions(local_only=True):
|
||||||
|
if dist.key.startswith(lc) and dist.key not in cwords[1:]:
|
||||||
|
installed.append(dist.key)
|
||||||
|
# if there are no dists installed, fall back to option completion
|
||||||
|
if installed:
|
||||||
|
for dist in installed:
|
||||||
|
print(dist)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
subcommand = commands_dict[subcommand_name]()
|
||||||
|
|
||||||
|
for opt in subcommand.parser.option_list_all:
|
||||||
|
if opt.help != optparse.SUPPRESS_HELP:
|
||||||
|
for opt_str in opt._long_opts + opt._short_opts:
|
||||||
|
options.append((opt_str, opt.nargs))
|
||||||
|
|
||||||
|
# filter out previously specified options from available options
|
||||||
|
prev_opts = [x.split('=')[0] for x in cwords[1:cword - 1]]
|
||||||
|
options = [(x, v) for (x, v) in options if x not in prev_opts]
|
||||||
|
# filter options by current input
|
||||||
|
options = [(k, v) for k, v in options if k.startswith(current)]
|
||||||
|
for option in options:
|
||||||
|
opt_label = option[0]
|
||||||
|
# append '=' to options which require args
|
||||||
|
if option[1] and option[0][:2] == "--":
|
||||||
|
opt_label += '='
|
||||||
|
print(opt_label)
|
||||||
|
else:
|
||||||
|
# show main parser options only when necessary
|
||||||
|
if current.startswith('-') or current.startswith('--'):
|
||||||
|
opts = [i.option_list for i in parser.option_groups]
|
||||||
|
opts.append(parser.option_list)
|
||||||
|
opts = (o for it in opts for o in it)
|
||||||
|
|
||||||
|
for opt in opts:
|
||||||
|
if opt.help != optparse.SUPPRESS_HELP:
|
||||||
|
subcommands += opt._long_opts + opt._short_opts
|
||||||
|
|
||||||
|
print(' '.join([x for x in subcommands if x.startswith(current)]))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def create_main_parser():
|
||||||
|
parser_kw = {
|
||||||
|
'usage': '\n%prog <command> [options]',
|
||||||
|
'add_help_option': False,
|
||||||
|
'formatter': UpdatingDefaultsHelpFormatter(),
|
||||||
|
'name': 'global',
|
||||||
|
'prog': get_prog(),
|
||||||
|
}
|
||||||
|
|
||||||
|
parser = ConfigOptionParser(**parser_kw)
|
||||||
|
parser.disable_interspersed_args()
|
||||||
|
|
||||||
|
pip_pkg_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
parser.version = 'pip %s from %s (python %s)' % (
|
||||||
|
__version__, pip_pkg_dir, sys.version[:3],
|
||||||
|
)
|
||||||
|
|
||||||
|
# add the general options
|
||||||
|
gen_opts = cmdoptions.make_option_group(cmdoptions.general_group, parser)
|
||||||
|
parser.add_option_group(gen_opts)
|
||||||
|
|
||||||
|
parser.main = True # so the help formatter knows
|
||||||
|
|
||||||
|
# create command listing for description
|
||||||
|
command_summaries = get_summaries()
|
||||||
|
description = [''] + ['%-27s %s' % (i, j) for i, j in command_summaries]
|
||||||
|
parser.description = '\n'.join(description)
|
||||||
|
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def parseopts(args):
|
||||||
|
parser = create_main_parser()
|
||||||
|
|
||||||
|
# Note: parser calls disable_interspersed_args(), so the result of this
|
||||||
|
# call is to split the initial args into the general options before the
|
||||||
|
# subcommand and everything else.
|
||||||
|
# For example:
|
||||||
|
# args: ['--timeout=5', 'install', '--user', 'INITools']
|
||||||
|
# general_options: ['--timeout==5']
|
||||||
|
# args_else: ['install', '--user', 'INITools']
|
||||||
|
general_options, args_else = parser.parse_args(args)
|
||||||
|
|
||||||
|
# --version
|
||||||
|
if general_options.version:
|
||||||
|
sys.stdout.write(parser.version)
|
||||||
|
sys.stdout.write(os.linesep)
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
# pip || pip help -> print_help()
|
||||||
|
if not args_else or (args_else[0] == 'help' and len(args_else) == 1):
|
||||||
|
parser.print_help()
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
# the subcommand name
|
||||||
|
cmd_name = args_else[0]
|
||||||
|
|
||||||
|
if cmd_name not in commands_dict:
|
||||||
|
guess = get_similar_commands(cmd_name)
|
||||||
|
|
||||||
|
msg = ['unknown command "%s"' % cmd_name]
|
||||||
|
if guess:
|
||||||
|
msg.append('maybe you meant "%s"' % guess)
|
||||||
|
|
||||||
|
raise CommandError(' - '.join(msg))
|
||||||
|
|
||||||
|
# all the args without the subcommand
|
||||||
|
cmd_args = args[:]
|
||||||
|
cmd_args.remove(cmd_name)
|
||||||
|
|
||||||
|
return cmd_name, cmd_args
|
||||||
|
|
||||||
|
|
||||||
|
def check_isolated(args):
|
||||||
|
isolated = False
|
||||||
|
|
||||||
|
if "--isolated" in args:
|
||||||
|
isolated = True
|
||||||
|
|
||||||
|
return isolated
|
||||||
|
|
||||||
|
|
||||||
|
def main(args=None):
|
||||||
|
if args is None:
|
||||||
|
args = sys.argv[1:]
|
||||||
|
|
||||||
|
# Configure our deprecation warnings to be sent through loggers
|
||||||
|
deprecation.install_warning_logger()
|
||||||
|
|
||||||
|
autocomplete()
|
||||||
|
|
||||||
|
try:
|
||||||
|
cmd_name, cmd_args = parseopts(args)
|
||||||
|
except PipError as exc:
|
||||||
|
sys.stderr.write("ERROR: %s" % exc)
|
||||||
|
sys.stderr.write(os.linesep)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Needed for locale.getpreferredencoding(False) to work
|
||||||
|
# in pip._internal.utils.encoding.auto_decode
|
||||||
|
try:
|
||||||
|
locale.setlocale(locale.LC_ALL, '')
|
||||||
|
except locale.Error as e:
|
||||||
|
# setlocale can apparently crash if locale are uninitialized
|
||||||
|
logger.debug("Ignoring error %s when setting locale", e)
|
||||||
|
command = commands_dict[cmd_name](isolated=check_isolated(cmd_args))
|
||||||
|
return command.main(cmd_args)
|
||||||
@ -0,0 +1,373 @@
|
|||||||
|
"""Base Command class, and related routines"""
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import logging.config
|
||||||
|
import optparse
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
from pip._internal import cmdoptions
|
||||||
|
from pip._internal.baseparser import (
|
||||||
|
ConfigOptionParser, UpdatingDefaultsHelpFormatter,
|
||||||
|
)
|
||||||
|
from pip._internal.compat import WINDOWS
|
||||||
|
from pip._internal.download import PipSession
|
||||||
|
from pip._internal.exceptions import (
|
||||||
|
BadCommand, CommandError, InstallationError, PreviousBuildDirError,
|
||||||
|
UninstallationError,
|
||||||
|
)
|
||||||
|
from pip._internal.index import PackageFinder
|
||||||
|
from pip._internal.locations import running_under_virtualenv
|
||||||
|
from pip._internal.req.req_file import parse_requirements
|
||||||
|
from pip._internal.req.req_install import InstallRequirement
|
||||||
|
from pip._internal.status_codes import (
|
||||||
|
ERROR, PREVIOUS_BUILD_DIR_ERROR, SUCCESS, UNKNOWN_ERROR,
|
||||||
|
VIRTUALENV_NOT_FOUND,
|
||||||
|
)
|
||||||
|
from pip._internal.utils import deprecation
|
||||||
|
from pip._internal.utils.logging import IndentingFormatter
|
||||||
|
from pip._internal.utils.misc import get_prog, normalize_path
|
||||||
|
from pip._internal.utils.outdated import pip_version_check
|
||||||
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
|
|
||||||
|
if MYPY_CHECK_RUNNING:
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
__all__ = ['Command']
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Command(object):
|
||||||
|
name = None # type: Optional[str]
|
||||||
|
usage = None # type: Optional[str]
|
||||||
|
hidden = False # type: bool
|
||||||
|
ignore_require_venv = False # type: bool
|
||||||
|
log_streams = ("ext://sys.stdout", "ext://sys.stderr")
|
||||||
|
|
||||||
|
def __init__(self, isolated=False):
|
||||||
|
parser_kw = {
|
||||||
|
'usage': self.usage,
|
||||||
|
'prog': '%s %s' % (get_prog(), self.name),
|
||||||
|
'formatter': UpdatingDefaultsHelpFormatter(),
|
||||||
|
'add_help_option': False,
|
||||||
|
'name': self.name,
|
||||||
|
'description': self.__doc__,
|
||||||
|
'isolated': isolated,
|
||||||
|
}
|
||||||
|
|
||||||
|
self.parser = ConfigOptionParser(**parser_kw)
|
||||||
|
|
||||||
|
# Commands should add options to this option group
|
||||||
|
optgroup_name = '%s Options' % self.name.capitalize()
|
||||||
|
self.cmd_opts = optparse.OptionGroup(self.parser, optgroup_name)
|
||||||
|
|
||||||
|
# Add the general options
|
||||||
|
gen_opts = cmdoptions.make_option_group(
|
||||||
|
cmdoptions.general_group,
|
||||||
|
self.parser,
|
||||||
|
)
|
||||||
|
self.parser.add_option_group(gen_opts)
|
||||||
|
|
||||||
|
def _build_session(self, options, retries=None, timeout=None):
|
||||||
|
session = PipSession(
|
||||||
|
cache=(
|
||||||
|
normalize_path(os.path.join(options.cache_dir, "http"))
|
||||||
|
if options.cache_dir else None
|
||||||
|
),
|
||||||
|
retries=retries if retries is not None else options.retries,
|
||||||
|
insecure_hosts=options.trusted_hosts,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Handle custom ca-bundles from the user
|
||||||
|
if options.cert:
|
||||||
|
session.verify = options.cert
|
||||||
|
|
||||||
|
# Handle SSL client certificate
|
||||||
|
if options.client_cert:
|
||||||
|
session.cert = options.client_cert
|
||||||
|
|
||||||
|
# Handle timeouts
|
||||||
|
if options.timeout or timeout:
|
||||||
|
session.timeout = (
|
||||||
|
timeout if timeout is not None else options.timeout
|
||||||
|
)
|
||||||
|
|
||||||
|
# Handle configured proxies
|
||||||
|
if options.proxy:
|
||||||
|
session.proxies = {
|
||||||
|
"http": options.proxy,
|
||||||
|
"https": options.proxy,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Determine if we can prompt the user for authentication or not
|
||||||
|
session.auth.prompting = not options.no_input
|
||||||
|
|
||||||
|
return session
|
||||||
|
|
||||||
|
def parse_args(self, args):
|
||||||
|
# factored out for testability
|
||||||
|
return self.parser.parse_args(args)
|
||||||
|
|
||||||
|
def main(self, args):
|
||||||
|
options, args = self.parse_args(args)
|
||||||
|
|
||||||
|
# Set verbosity so that it can be used elsewhere.
|
||||||
|
self.verbosity = options.verbose - options.quiet
|
||||||
|
|
||||||
|
if self.verbosity >= 1:
|
||||||
|
level = "DEBUG"
|
||||||
|
elif self.verbosity == -1:
|
||||||
|
level = "WARNING"
|
||||||
|
elif self.verbosity == -2:
|
||||||
|
level = "ERROR"
|
||||||
|
elif self.verbosity <= -3:
|
||||||
|
level = "CRITICAL"
|
||||||
|
else:
|
||||||
|
level = "INFO"
|
||||||
|
|
||||||
|
# The root logger should match the "console" level *unless* we
|
||||||
|
# specified "--log" to send debug logs to a file.
|
||||||
|
root_level = level
|
||||||
|
if options.log:
|
||||||
|
root_level = "DEBUG"
|
||||||
|
|
||||||
|
logger_class = "pip._internal.utils.logging.ColorizedStreamHandler"
|
||||||
|
handler_class = "pip._internal.utils.logging.BetterRotatingFileHandler"
|
||||||
|
|
||||||
|
logging.config.dictConfig({
|
||||||
|
"version": 1,
|
||||||
|
"disable_existing_loggers": False,
|
||||||
|
"filters": {
|
||||||
|
"exclude_warnings": {
|
||||||
|
"()": "pip._internal.utils.logging.MaxLevelFilter",
|
||||||
|
"level": logging.WARNING,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"formatters": {
|
||||||
|
"indent": {
|
||||||
|
"()": IndentingFormatter,
|
||||||
|
"format": "%(message)s",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"handlers": {
|
||||||
|
"console": {
|
||||||
|
"level": level,
|
||||||
|
"class": logger_class,
|
||||||
|
"no_color": options.no_color,
|
||||||
|
"stream": self.log_streams[0],
|
||||||
|
"filters": ["exclude_warnings"],
|
||||||
|
"formatter": "indent",
|
||||||
|
},
|
||||||
|
"console_errors": {
|
||||||
|
"level": "WARNING",
|
||||||
|
"class": logger_class,
|
||||||
|
"no_color": options.no_color,
|
||||||
|
"stream": self.log_streams[1],
|
||||||
|
"formatter": "indent",
|
||||||
|
},
|
||||||
|
"user_log": {
|
||||||
|
"level": "DEBUG",
|
||||||
|
"class": handler_class,
|
||||||
|
"filename": options.log or "/dev/null",
|
||||||
|
"delay": True,
|
||||||
|
"formatter": "indent",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"level": root_level,
|
||||||
|
"handlers": list(filter(None, [
|
||||||
|
"console",
|
||||||
|
"console_errors",
|
||||||
|
"user_log" if options.log else None,
|
||||||
|
])),
|
||||||
|
},
|
||||||
|
# Disable any logging besides WARNING unless we have DEBUG level
|
||||||
|
# logging enabled. These use both pip._vendor and the bare names
|
||||||
|
# for the case where someone unbundles our libraries.
|
||||||
|
"loggers": {
|
||||||
|
name: {
|
||||||
|
"level": (
|
||||||
|
"WARNING" if level in ["INFO", "ERROR"] else "DEBUG"
|
||||||
|
)
|
||||||
|
} for name in [
|
||||||
|
"pip._vendor", "distlib", "requests", "urllib3"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if sys.version_info[:2] == (3, 3):
|
||||||
|
warnings.warn(
|
||||||
|
"Python 3.3 supported has been deprecated and support for it "
|
||||||
|
"will be dropped in the future. Please upgrade your Python.",
|
||||||
|
deprecation.RemovedInPip11Warning,
|
||||||
|
)
|
||||||
|
|
||||||
|
# TODO: try to get these passing down from the command?
|
||||||
|
# without resorting to os.environ to hold these.
|
||||||
|
|
||||||
|
if options.no_input:
|
||||||
|
os.environ['PIP_NO_INPUT'] = '1'
|
||||||
|
|
||||||
|
if options.exists_action:
|
||||||
|
os.environ['PIP_EXISTS_ACTION'] = ' '.join(options.exists_action)
|
||||||
|
|
||||||
|
if options.require_venv and not self.ignore_require_venv:
|
||||||
|
# If a venv is required check if it can really be found
|
||||||
|
if not running_under_virtualenv():
|
||||||
|
logger.critical(
|
||||||
|
'Could not find an activated virtualenv (required).'
|
||||||
|
)
|
||||||
|
sys.exit(VIRTUALENV_NOT_FOUND)
|
||||||
|
|
||||||
|
original_root_handlers = set(logging.root.handlers)
|
||||||
|
|
||||||
|
try:
|
||||||
|
status = self.run(options, args)
|
||||||
|
# FIXME: all commands should return an exit status
|
||||||
|
# and when it is done, isinstance is not needed anymore
|
||||||
|
if isinstance(status, int):
|
||||||
|
return status
|
||||||
|
except PreviousBuildDirError as exc:
|
||||||
|
logger.critical(str(exc))
|
||||||
|
logger.debug('Exception information:', exc_info=True)
|
||||||
|
|
||||||
|
return PREVIOUS_BUILD_DIR_ERROR
|
||||||
|
except (InstallationError, UninstallationError, BadCommand) as exc:
|
||||||
|
logger.critical(str(exc))
|
||||||
|
logger.debug('Exception information:', exc_info=True)
|
||||||
|
|
||||||
|
return ERROR
|
||||||
|
except CommandError as exc:
|
||||||
|
logger.critical('ERROR: %s', exc)
|
||||||
|
logger.debug('Exception information:', exc_info=True)
|
||||||
|
|
||||||
|
return ERROR
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
logger.critical('Operation cancelled by user')
|
||||||
|
logger.debug('Exception information:', exc_info=True)
|
||||||
|
|
||||||
|
return ERROR
|
||||||
|
except:
|
||||||
|
logger.critical('Exception:', exc_info=True)
|
||||||
|
|
||||||
|
return UNKNOWN_ERROR
|
||||||
|
finally:
|
||||||
|
# Check if we're using the latest version of pip available
|
||||||
|
if (not options.disable_pip_version_check and not
|
||||||
|
getattr(options, "no_index", False)):
|
||||||
|
with self._build_session(
|
||||||
|
options,
|
||||||
|
retries=0,
|
||||||
|
timeout=min(5, options.timeout)) as session:
|
||||||
|
pip_version_check(session, options)
|
||||||
|
# Avoid leaking loggers
|
||||||
|
for handler in set(logging.root.handlers) - original_root_handlers:
|
||||||
|
# this method benefit from the Logger class internal lock
|
||||||
|
logging.root.removeHandler(handler)
|
||||||
|
|
||||||
|
return SUCCESS
|
||||||
|
|
||||||
|
|
||||||
|
class RequirementCommand(Command):
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def populate_requirement_set(requirement_set, args, options, finder,
|
||||||
|
session, name, wheel_cache):
|
||||||
|
"""
|
||||||
|
Marshal cmd line args into a requirement set.
|
||||||
|
"""
|
||||||
|
# NOTE: As a side-effect, options.require_hashes and
|
||||||
|
# requirement_set.require_hashes may be updated
|
||||||
|
|
||||||
|
for filename in options.constraints:
|
||||||
|
for req_to_add in parse_requirements(
|
||||||
|
filename,
|
||||||
|
constraint=True, finder=finder, options=options,
|
||||||
|
session=session, wheel_cache=wheel_cache):
|
||||||
|
req_to_add.is_direct = True
|
||||||
|
requirement_set.add_requirement(req_to_add)
|
||||||
|
|
||||||
|
for req in args:
|
||||||
|
req_to_add = InstallRequirement.from_line(
|
||||||
|
req, None, isolated=options.isolated_mode,
|
||||||
|
wheel_cache=wheel_cache
|
||||||
|
)
|
||||||
|
req_to_add.is_direct = True
|
||||||
|
requirement_set.add_requirement(req_to_add)
|
||||||
|
|
||||||
|
for req in options.editables:
|
||||||
|
req_to_add = InstallRequirement.from_editable(
|
||||||
|
req,
|
||||||
|
isolated=options.isolated_mode,
|
||||||
|
wheel_cache=wheel_cache
|
||||||
|
)
|
||||||
|
req_to_add.is_direct = True
|
||||||
|
requirement_set.add_requirement(req_to_add)
|
||||||
|
|
||||||
|
for filename in options.requirements:
|
||||||
|
for req_to_add in parse_requirements(
|
||||||
|
filename,
|
||||||
|
finder=finder, options=options, session=session,
|
||||||
|
wheel_cache=wheel_cache):
|
||||||
|
req_to_add.is_direct = True
|
||||||
|
requirement_set.add_requirement(req_to_add)
|
||||||
|
# If --require-hashes was a line in a requirements file, tell
|
||||||
|
# RequirementSet about it:
|
||||||
|
requirement_set.require_hashes = options.require_hashes
|
||||||
|
|
||||||
|
if not (args or options.editables or options.requirements):
|
||||||
|
opts = {'name': name}
|
||||||
|
if options.find_links:
|
||||||
|
raise CommandError(
|
||||||
|
'You must give at least one requirement to %(name)s '
|
||||||
|
'(maybe you meant "pip %(name)s %(links)s"?)' %
|
||||||
|
dict(opts, links=' '.join(options.find_links)))
|
||||||
|
else:
|
||||||
|
raise CommandError(
|
||||||
|
'You must give at least one requirement to %(name)s '
|
||||||
|
'(see "pip help %(name)s")' % opts)
|
||||||
|
|
||||||
|
# On Windows, any operation modifying pip should be run as:
|
||||||
|
# python -m pip ...
|
||||||
|
# See https://github.com/pypa/pip/issues/1299 for more discussion
|
||||||
|
should_show_use_python_msg = (
|
||||||
|
WINDOWS and
|
||||||
|
requirement_set.has_requirement("pip") and
|
||||||
|
os.path.basename(sys.argv[0]).startswith("pip")
|
||||||
|
)
|
||||||
|
if should_show_use_python_msg:
|
||||||
|
new_command = [
|
||||||
|
sys.executable, "-m", "pip"
|
||||||
|
] + sys.argv[1:]
|
||||||
|
raise CommandError(
|
||||||
|
'To modify pip, please run the following command:\n{}'
|
||||||
|
.format(" ".join(new_command))
|
||||||
|
)
|
||||||
|
|
||||||
|
def _build_package_finder(self, options, session,
|
||||||
|
platform=None, python_versions=None,
|
||||||
|
abi=None, implementation=None):
|
||||||
|
"""
|
||||||
|
Create a package finder appropriate to this requirement command.
|
||||||
|
"""
|
||||||
|
index_urls = [options.index_url] + options.extra_index_urls
|
||||||
|
if options.no_index:
|
||||||
|
logger.debug('Ignoring indexes: %s', ','.join(index_urls))
|
||||||
|
index_urls = []
|
||||||
|
|
||||||
|
return PackageFinder(
|
||||||
|
find_links=options.find_links,
|
||||||
|
format_control=options.format_control,
|
||||||
|
index_urls=index_urls,
|
||||||
|
trusted_hosts=options.trusted_hosts,
|
||||||
|
allow_all_prereleases=options.pre,
|
||||||
|
process_dependency_links=options.process_dependency_links,
|
||||||
|
session=session,
|
||||||
|
platform=platform,
|
||||||
|
versions=python_versions,
|
||||||
|
abi=abi,
|
||||||
|
implementation=implementation,
|
||||||
|
)
|
||||||
@ -0,0 +1,240 @@
|
|||||||
|
"""Base option parser setup"""
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import optparse
|
||||||
|
import sys
|
||||||
|
import textwrap
|
||||||
|
from distutils.util import strtobool
|
||||||
|
|
||||||
|
from pip._vendor.six import string_types
|
||||||
|
|
||||||
|
from pip._internal.compat import get_terminal_size
|
||||||
|
from pip._internal.configuration import Configuration, ConfigurationError
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class PrettyHelpFormatter(optparse.IndentedHelpFormatter):
|
||||||
|
"""A prettier/less verbose help formatter for optparse."""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
# help position must be aligned with __init__.parseopts.description
|
||||||
|
kwargs['max_help_position'] = 30
|
||||||
|
kwargs['indent_increment'] = 1
|
||||||
|
kwargs['width'] = get_terminal_size()[0] - 2
|
||||||
|
optparse.IndentedHelpFormatter.__init__(self, *args, **kwargs)
|
||||||
|
|
||||||
|
def format_option_strings(self, option):
|
||||||
|
return self._format_option_strings(option, ' <%s>', ', ')
|
||||||
|
|
||||||
|
def _format_option_strings(self, option, mvarfmt=' <%s>', optsep=', '):
|
||||||
|
"""
|
||||||
|
Return a comma-separated list of option strings and metavars.
|
||||||
|
|
||||||
|
:param option: tuple of (short opt, long opt), e.g: ('-f', '--format')
|
||||||
|
:param mvarfmt: metavar format string - evaluated as mvarfmt % metavar
|
||||||
|
:param optsep: separator
|
||||||
|
"""
|
||||||
|
opts = []
|
||||||
|
|
||||||
|
if option._short_opts:
|
||||||
|
opts.append(option._short_opts[0])
|
||||||
|
if option._long_opts:
|
||||||
|
opts.append(option._long_opts[0])
|
||||||
|
if len(opts) > 1:
|
||||||
|
opts.insert(1, optsep)
|
||||||
|
|
||||||
|
if option.takes_value():
|
||||||
|
metavar = option.metavar or option.dest.lower()
|
||||||
|
opts.append(mvarfmt % metavar.lower())
|
||||||
|
|
||||||
|
return ''.join(opts)
|
||||||
|
|
||||||
|
def format_heading(self, heading):
|
||||||
|
if heading == 'Options':
|
||||||
|
return ''
|
||||||
|
return heading + ':\n'
|
||||||
|
|
||||||
|
def format_usage(self, usage):
|
||||||
|
"""
|
||||||
|
Ensure there is only one newline between usage and the first heading
|
||||||
|
if there is no description.
|
||||||
|
"""
|
||||||
|
msg = '\nUsage: %s\n' % self.indent_lines(textwrap.dedent(usage), " ")
|
||||||
|
return msg
|
||||||
|
|
||||||
|
def format_description(self, description):
|
||||||
|
# leave full control over description to us
|
||||||
|
if description:
|
||||||
|
if hasattr(self.parser, 'main'):
|
||||||
|
label = 'Commands'
|
||||||
|
else:
|
||||||
|
label = 'Description'
|
||||||
|
# some doc strings have initial newlines, some don't
|
||||||
|
description = description.lstrip('\n')
|
||||||
|
# some doc strings have final newlines and spaces, some don't
|
||||||
|
description = description.rstrip()
|
||||||
|
# dedent, then reindent
|
||||||
|
description = self.indent_lines(textwrap.dedent(description), " ")
|
||||||
|
description = '%s:\n%s\n' % (label, description)
|
||||||
|
return description
|
||||||
|
else:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def format_epilog(self, epilog):
|
||||||
|
# leave full control over epilog to us
|
||||||
|
if epilog:
|
||||||
|
return epilog
|
||||||
|
else:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def indent_lines(self, text, indent):
|
||||||
|
new_lines = [indent + line for line in text.split('\n')]
|
||||||
|
return "\n".join(new_lines)
|
||||||
|
|
||||||
|
|
||||||
|
class UpdatingDefaultsHelpFormatter(PrettyHelpFormatter):
|
||||||
|
"""Custom help formatter for use in ConfigOptionParser.
|
||||||
|
|
||||||
|
This is updates the defaults before expanding them, allowing
|
||||||
|
them to show up correctly in the help listing.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def expand_default(self, option):
|
||||||
|
if self.parser is not None:
|
||||||
|
self.parser._update_defaults(self.parser.defaults)
|
||||||
|
return optparse.IndentedHelpFormatter.expand_default(self, option)
|
||||||
|
|
||||||
|
|
||||||
|
class CustomOptionParser(optparse.OptionParser):
|
||||||
|
|
||||||
|
def insert_option_group(self, idx, *args, **kwargs):
|
||||||
|
"""Insert an OptionGroup at a given position."""
|
||||||
|
group = self.add_option_group(*args, **kwargs)
|
||||||
|
|
||||||
|
self.option_groups.pop()
|
||||||
|
self.option_groups.insert(idx, group)
|
||||||
|
|
||||||
|
return group
|
||||||
|
|
||||||
|
@property
|
||||||
|
def option_list_all(self):
|
||||||
|
"""Get a list of all options, including those in option groups."""
|
||||||
|
res = self.option_list[:]
|
||||||
|
for i in self.option_groups:
|
||||||
|
res.extend(i.option_list)
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigOptionParser(CustomOptionParser):
|
||||||
|
"""Custom option parser which updates its defaults by checking the
|
||||||
|
configuration files and environmental variables"""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.name = kwargs.pop('name')
|
||||||
|
|
||||||
|
isolated = kwargs.pop("isolated", False)
|
||||||
|
self.config = Configuration(isolated)
|
||||||
|
|
||||||
|
assert self.name
|
||||||
|
optparse.OptionParser.__init__(self, *args, **kwargs)
|
||||||
|
|
||||||
|
def check_default(self, option, key, val):
|
||||||
|
try:
|
||||||
|
return option.check_value(key, val)
|
||||||
|
except optparse.OptionValueError as exc:
|
||||||
|
print("An error occurred during configuration: %s" % exc)
|
||||||
|
sys.exit(3)
|
||||||
|
|
||||||
|
def _get_ordered_configuration_items(self):
|
||||||
|
# Configuration gives keys in an unordered manner. Order them.
|
||||||
|
override_order = ["global", self.name, ":env:"]
|
||||||
|
|
||||||
|
# Pool the options into different groups
|
||||||
|
section_items = {name: [] for name in override_order}
|
||||||
|
for section_key, val in self.config.items():
|
||||||
|
# ignore empty values
|
||||||
|
if not val:
|
||||||
|
logger.debug(
|
||||||
|
"Ignoring configuration key '%s' as it's value is empty.",
|
||||||
|
section_key
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
section, key = section_key.split(".", 1)
|
||||||
|
if section in override_order:
|
||||||
|
section_items[section].append((key, val))
|
||||||
|
|
||||||
|
# Yield each group in their override order
|
||||||
|
for section in override_order:
|
||||||
|
for key, val in section_items[section]:
|
||||||
|
yield key, val
|
||||||
|
|
||||||
|
def _update_defaults(self, defaults):
|
||||||
|
"""Updates the given defaults with values from the config files and
|
||||||
|
the environ. Does a little special handling for certain types of
|
||||||
|
options (lists)."""
|
||||||
|
|
||||||
|
# Accumulate complex default state.
|
||||||
|
self.values = optparse.Values(self.defaults)
|
||||||
|
late_eval = set()
|
||||||
|
# Then set the options with those values
|
||||||
|
for key, val in self._get_ordered_configuration_items():
|
||||||
|
# '--' because configuration supports only long names
|
||||||
|
option = self.get_option('--' + key)
|
||||||
|
|
||||||
|
# Ignore options not present in this parser. E.g. non-globals put
|
||||||
|
# in [global] by users that want them to apply to all applicable
|
||||||
|
# commands.
|
||||||
|
if option is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if option.action in ('store_true', 'store_false', 'count'):
|
||||||
|
val = strtobool(val)
|
||||||
|
elif option.action == 'append':
|
||||||
|
val = val.split()
|
||||||
|
val = [self.check_default(option, key, v) for v in val]
|
||||||
|
elif option.action == 'callback':
|
||||||
|
late_eval.add(option.dest)
|
||||||
|
opt_str = option.get_opt_string()
|
||||||
|
val = option.convert_value(opt_str, val)
|
||||||
|
# From take_action
|
||||||
|
args = option.callback_args or ()
|
||||||
|
kwargs = option.callback_kwargs or {}
|
||||||
|
option.callback(option, opt_str, val, self, *args, **kwargs)
|
||||||
|
else:
|
||||||
|
val = self.check_default(option, key, val)
|
||||||
|
|
||||||
|
defaults[option.dest] = val
|
||||||
|
|
||||||
|
for key in late_eval:
|
||||||
|
defaults[key] = getattr(self.values, key)
|
||||||
|
self.values = None
|
||||||
|
return defaults
|
||||||
|
|
||||||
|
def get_default_values(self):
|
||||||
|
"""Overriding to make updating the defaults after instantiation of
|
||||||
|
the option parser possible, _update_defaults() does the dirty work."""
|
||||||
|
if not self.process_default_values:
|
||||||
|
# Old, pre-Optik 1.5 behaviour.
|
||||||
|
return optparse.Values(self.defaults)
|
||||||
|
|
||||||
|
# Load the configuration, or error out in case of an error
|
||||||
|
try:
|
||||||
|
self.config.load()
|
||||||
|
except ConfigurationError as err:
|
||||||
|
self.exit(2, err.args[0])
|
||||||
|
|
||||||
|
defaults = self._update_defaults(self.defaults.copy()) # ours
|
||||||
|
for option in self._get_all_options():
|
||||||
|
default = defaults.get(option.dest)
|
||||||
|
if isinstance(default, string_types):
|
||||||
|
opt_str = option.get_opt_string()
|
||||||
|
defaults[option.dest] = option.check_value(opt_str, default)
|
||||||
|
return optparse.Values(defaults)
|
||||||
|
|
||||||
|
def error(self, msg):
|
||||||
|
self.print_usage(sys.stderr)
|
||||||
|
self.exit(2, "%s\n" % msg)
|
||||||
@ -0,0 +1,92 @@
|
|||||||
|
"""Build Environment used for isolation during sdist building
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
from distutils.sysconfig import get_python_lib
|
||||||
|
from sysconfig import get_paths
|
||||||
|
|
||||||
|
from pip._internal.utils.temp_dir import TempDirectory
|
||||||
|
|
||||||
|
|
||||||
|
class BuildEnvironment(object):
|
||||||
|
"""Creates and manages an isolated environment to install build deps
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, no_clean):
|
||||||
|
self._temp_dir = TempDirectory(kind="build-env")
|
||||||
|
self._no_clean = no_clean
|
||||||
|
|
||||||
|
@property
|
||||||
|
def path(self):
|
||||||
|
return self._temp_dir.path
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
self._temp_dir.create()
|
||||||
|
|
||||||
|
self.save_path = os.environ.get('PATH', None)
|
||||||
|
self.save_pythonpath = os.environ.get('PYTHONPATH', None)
|
||||||
|
self.save_nousersite = os.environ.get('PYTHONNOUSERSITE', None)
|
||||||
|
|
||||||
|
install_scheme = 'nt' if (os.name == 'nt') else 'posix_prefix'
|
||||||
|
install_dirs = get_paths(install_scheme, vars={
|
||||||
|
'base': self.path,
|
||||||
|
'platbase': self.path,
|
||||||
|
})
|
||||||
|
|
||||||
|
scripts = install_dirs['scripts']
|
||||||
|
if self.save_path:
|
||||||
|
os.environ['PATH'] = scripts + os.pathsep + self.save_path
|
||||||
|
else:
|
||||||
|
os.environ['PATH'] = scripts + os.pathsep + os.defpath
|
||||||
|
|
||||||
|
# Note: prefer distutils' sysconfig to get the
|
||||||
|
# library paths so PyPy is correctly supported.
|
||||||
|
purelib = get_python_lib(plat_specific=0, prefix=self.path)
|
||||||
|
platlib = get_python_lib(plat_specific=1, prefix=self.path)
|
||||||
|
if purelib == platlib:
|
||||||
|
lib_dirs = purelib
|
||||||
|
else:
|
||||||
|
lib_dirs = purelib + os.pathsep + platlib
|
||||||
|
if self.save_pythonpath:
|
||||||
|
os.environ['PYTHONPATH'] = lib_dirs + os.pathsep + \
|
||||||
|
self.save_pythonpath
|
||||||
|
else:
|
||||||
|
os.environ['PYTHONPATH'] = lib_dirs
|
||||||
|
|
||||||
|
os.environ['PYTHONNOUSERSITE'] = '1'
|
||||||
|
|
||||||
|
return self.path
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
if not self._no_clean:
|
||||||
|
self._temp_dir.cleanup()
|
||||||
|
|
||||||
|
def restore_var(varname, old_value):
|
||||||
|
if old_value is None:
|
||||||
|
os.environ.pop(varname, None)
|
||||||
|
else:
|
||||||
|
os.environ[varname] = old_value
|
||||||
|
|
||||||
|
restore_var('PATH', self.save_path)
|
||||||
|
restore_var('PYTHONPATH', self.save_pythonpath)
|
||||||
|
restore_var('PYTHONNOUSERSITE', self.save_nousersite)
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
self._temp_dir.cleanup()
|
||||||
|
|
||||||
|
|
||||||
|
class NoOpBuildEnvironment(BuildEnvironment):
|
||||||
|
"""A no-op drop-in replacement for BuildEnvironment
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, no_clean):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
pass
|
||||||
@ -0,0 +1,202 @@
|
|||||||
|
"""Cache Management
|
||||||
|
"""
|
||||||
|
|
||||||
|
import errno
|
||||||
|
import hashlib
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
from pip._vendor.packaging.utils import canonicalize_name
|
||||||
|
|
||||||
|
from pip._internal import index
|
||||||
|
from pip._internal.compat import expanduser
|
||||||
|
from pip._internal.download import path_to_url
|
||||||
|
from pip._internal.utils.temp_dir import TempDirectory
|
||||||
|
from pip._internal.wheel import InvalidWheelFilename, Wheel
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Cache(object):
|
||||||
|
"""An abstract class - provides cache directories for data from links
|
||||||
|
|
||||||
|
|
||||||
|
:param cache_dir: The root of the cache.
|
||||||
|
:param format_control: A pip.index.FormatControl object to limit
|
||||||
|
binaries being read from the cache.
|
||||||
|
:param allowed_formats: which formats of files the cache should store.
|
||||||
|
('binary' and 'source' are the only allowed values)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, cache_dir, format_control, allowed_formats):
|
||||||
|
super(Cache, self).__init__()
|
||||||
|
self.cache_dir = expanduser(cache_dir) if cache_dir else None
|
||||||
|
self.format_control = format_control
|
||||||
|
self.allowed_formats = allowed_formats
|
||||||
|
|
||||||
|
_valid_formats = {"source", "binary"}
|
||||||
|
assert self.allowed_formats.union(_valid_formats) == _valid_formats
|
||||||
|
|
||||||
|
def _get_cache_path_parts(self, link):
|
||||||
|
"""Get parts of part that must be os.path.joined with cache_dir
|
||||||
|
"""
|
||||||
|
|
||||||
|
# We want to generate an url to use as our cache key, we don't want to
|
||||||
|
# just re-use the URL because it might have other items in the fragment
|
||||||
|
# and we don't care about those.
|
||||||
|
key_parts = [link.url_without_fragment]
|
||||||
|
if link.hash_name is not None and link.hash is not None:
|
||||||
|
key_parts.append("=".join([link.hash_name, link.hash]))
|
||||||
|
key_url = "#".join(key_parts)
|
||||||
|
|
||||||
|
# Encode our key url with sha224, we'll use this because it has similar
|
||||||
|
# security properties to sha256, but with a shorter total output (and
|
||||||
|
# thus less secure). However the differences don't make a lot of
|
||||||
|
# difference for our use case here.
|
||||||
|
hashed = hashlib.sha224(key_url.encode()).hexdigest()
|
||||||
|
|
||||||
|
# We want to nest the directories some to prevent having a ton of top
|
||||||
|
# level directories where we might run out of sub directories on some
|
||||||
|
# FS.
|
||||||
|
parts = [hashed[:2], hashed[2:4], hashed[4:6], hashed[6:]]
|
||||||
|
|
||||||
|
return parts
|
||||||
|
|
||||||
|
def _get_candidates(self, link, package_name):
|
||||||
|
can_not_cache = (
|
||||||
|
not self.cache_dir or
|
||||||
|
not package_name or
|
||||||
|
not link
|
||||||
|
)
|
||||||
|
if can_not_cache:
|
||||||
|
return []
|
||||||
|
|
||||||
|
canonical_name = canonicalize_name(package_name)
|
||||||
|
formats = index.fmt_ctl_formats(
|
||||||
|
self.format_control, canonical_name
|
||||||
|
)
|
||||||
|
if not self.allowed_formats.intersection(formats):
|
||||||
|
return []
|
||||||
|
|
||||||
|
root = self.get_path_for_link(link)
|
||||||
|
try:
|
||||||
|
return os.listdir(root)
|
||||||
|
except OSError as err:
|
||||||
|
if err.errno in {errno.ENOENT, errno.ENOTDIR}:
|
||||||
|
return []
|
||||||
|
raise
|
||||||
|
|
||||||
|
def get_path_for_link(self, link):
|
||||||
|
"""Return a directory to store cached items in for link.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def get(self, link, package_name):
|
||||||
|
"""Returns a link to a cached item if it exists, otherwise returns the
|
||||||
|
passed link.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def _link_for_candidate(self, link, candidate):
|
||||||
|
root = self.get_path_for_link(link)
|
||||||
|
path = os.path.join(root, candidate)
|
||||||
|
|
||||||
|
return index.Link(path_to_url(path))
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleWheelCache(Cache):
|
||||||
|
"""A cache of wheels for future installs.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, cache_dir, format_control):
|
||||||
|
super(SimpleWheelCache, self).__init__(
|
||||||
|
cache_dir, format_control, {"binary"}
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_path_for_link(self, link):
|
||||||
|
"""Return a directory to store cached wheels for link
|
||||||
|
|
||||||
|
Because there are M wheels for any one sdist, we provide a directory
|
||||||
|
to cache them in, and then consult that directory when looking up
|
||||||
|
cache hits.
|
||||||
|
|
||||||
|
We only insert things into the cache if they have plausible version
|
||||||
|
numbers, so that we don't contaminate the cache with things that were
|
||||||
|
not unique. E.g. ./package might have dozens of installs done for it
|
||||||
|
and build a version of 0.0...and if we built and cached a wheel, we'd
|
||||||
|
end up using the same wheel even if the source has been edited.
|
||||||
|
|
||||||
|
:param link: The link of the sdist for which this will cache wheels.
|
||||||
|
"""
|
||||||
|
parts = self._get_cache_path_parts(link)
|
||||||
|
|
||||||
|
# Store wheels within the root cache_dir
|
||||||
|
return os.path.join(self.cache_dir, "wheels", *parts)
|
||||||
|
|
||||||
|
def get(self, link, package_name):
|
||||||
|
candidates = []
|
||||||
|
|
||||||
|
for wheel_name in self._get_candidates(link, package_name):
|
||||||
|
try:
|
||||||
|
wheel = Wheel(wheel_name)
|
||||||
|
except InvalidWheelFilename:
|
||||||
|
continue
|
||||||
|
if not wheel.supported():
|
||||||
|
# Built for a different python/arch/etc
|
||||||
|
continue
|
||||||
|
candidates.append((wheel.support_index_min(), wheel_name))
|
||||||
|
|
||||||
|
if not candidates:
|
||||||
|
return link
|
||||||
|
|
||||||
|
return self._link_for_candidate(link, min(candidates)[1])
|
||||||
|
|
||||||
|
|
||||||
|
class EphemWheelCache(SimpleWheelCache):
|
||||||
|
"""A SimpleWheelCache that creates it's own temporary cache directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, format_control):
|
||||||
|
self._temp_dir = TempDirectory(kind="ephem-wheel-cache")
|
||||||
|
self._temp_dir.create()
|
||||||
|
|
||||||
|
super(EphemWheelCache, self).__init__(
|
||||||
|
self._temp_dir.path, format_control
|
||||||
|
)
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
self._temp_dir.cleanup()
|
||||||
|
|
||||||
|
|
||||||
|
class WheelCache(Cache):
|
||||||
|
"""Wraps EphemWheelCache and SimpleWheelCache into a single Cache
|
||||||
|
|
||||||
|
This Cache allows for gracefully degradation, using the ephem wheel cache
|
||||||
|
when a certain link is not found in the simple wheel cache first.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, cache_dir, format_control):
|
||||||
|
super(WheelCache, self).__init__(
|
||||||
|
cache_dir, format_control, {'binary'}
|
||||||
|
)
|
||||||
|
self._wheel_cache = SimpleWheelCache(cache_dir, format_control)
|
||||||
|
self._ephem_cache = EphemWheelCache(format_control)
|
||||||
|
|
||||||
|
def get_path_for_link(self, link):
|
||||||
|
return self._wheel_cache.get_path_for_link(link)
|
||||||
|
|
||||||
|
def get_ephem_path_for_link(self, link):
|
||||||
|
return self._ephem_cache.get_path_for_link(link)
|
||||||
|
|
||||||
|
def get(self, link, package_name):
|
||||||
|
retval = self._wheel_cache.get(link, package_name)
|
||||||
|
if retval is link:
|
||||||
|
retval = self._ephem_cache.get(link, package_name)
|
||||||
|
return retval
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
self._wheel_cache.cleanup()
|
||||||
|
self._ephem_cache.cleanup()
|
||||||
@ -0,0 +1,609 @@
|
|||||||
|
"""
|
||||||
|
shared options and groups
|
||||||
|
|
||||||
|
The principle here is to define options once, but *not* instantiate them
|
||||||
|
globally. One reason being that options with action='append' can carry state
|
||||||
|
between parses. pip parses general options twice internally, and shouldn't
|
||||||
|
pass on state. To be consistent, all options will follow this design.
|
||||||
|
|
||||||
|
"""
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import warnings
|
||||||
|
from functools import partial
|
||||||
|
from optparse import SUPPRESS_HELP, Option, OptionGroup
|
||||||
|
|
||||||
|
from pip._internal.index import (
|
||||||
|
FormatControl, fmt_ctl_handle_mutual_exclude, fmt_ctl_no_binary,
|
||||||
|
)
|
||||||
|
from pip._internal.locations import USER_CACHE_DIR, src_prefix
|
||||||
|
from pip._internal.models import PyPI
|
||||||
|
from pip._internal.utils.hashes import STRONG_HASHES
|
||||||
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
|
from pip._internal.utils.ui import BAR_TYPES
|
||||||
|
|
||||||
|
if MYPY_CHECK_RUNNING:
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
|
def make_option_group(group, parser):
|
||||||
|
"""
|
||||||
|
Return an OptionGroup object
|
||||||
|
group -- assumed to be dict with 'name' and 'options' keys
|
||||||
|
parser -- an optparse Parser
|
||||||
|
"""
|
||||||
|
option_group = OptionGroup(parser, group['name'])
|
||||||
|
for option in group['options']:
|
||||||
|
option_group.add_option(option())
|
||||||
|
return option_group
|
||||||
|
|
||||||
|
|
||||||
|
def check_install_build_global(options, check_options=None):
|
||||||
|
"""Disable wheels if per-setup.py call options are set.
|
||||||
|
|
||||||
|
:param options: The OptionParser options to update.
|
||||||
|
:param check_options: The options to check, if not supplied defaults to
|
||||||
|
options.
|
||||||
|
"""
|
||||||
|
if check_options is None:
|
||||||
|
check_options = options
|
||||||
|
|
||||||
|
def getname(n):
|
||||||
|
return getattr(check_options, n, None)
|
||||||
|
names = ["build_options", "global_options", "install_options"]
|
||||||
|
if any(map(getname, names)):
|
||||||
|
control = options.format_control
|
||||||
|
fmt_ctl_no_binary(control)
|
||||||
|
warnings.warn(
|
||||||
|
'Disabling all use of wheels due to the use of --build-options '
|
||||||
|
'/ --global-options / --install-options.', stacklevel=2,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
###########
|
||||||
|
# options #
|
||||||
|
###########
|
||||||
|
|
||||||
|
help_ = partial(
|
||||||
|
Option,
|
||||||
|
'-h', '--help',
|
||||||
|
dest='help',
|
||||||
|
action='help',
|
||||||
|
help='Show help.',
|
||||||
|
) # type: Any
|
||||||
|
|
||||||
|
isolated_mode = partial(
|
||||||
|
Option,
|
||||||
|
"--isolated",
|
||||||
|
dest="isolated_mode",
|
||||||
|
action="store_true",
|
||||||
|
default=False,
|
||||||
|
help=(
|
||||||
|
"Run pip in an isolated mode, ignoring environment variables and user "
|
||||||
|
"configuration."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
require_virtualenv = partial(
|
||||||
|
Option,
|
||||||
|
# Run only if inside a virtualenv, bail if not.
|
||||||
|
'--require-virtualenv', '--require-venv',
|
||||||
|
dest='require_venv',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help=SUPPRESS_HELP
|
||||||
|
) # type: Any
|
||||||
|
|
||||||
|
verbose = partial(
|
||||||
|
Option,
|
||||||
|
'-v', '--verbose',
|
||||||
|
dest='verbose',
|
||||||
|
action='count',
|
||||||
|
default=0,
|
||||||
|
help='Give more output. Option is additive, and can be used up to 3 times.'
|
||||||
|
)
|
||||||
|
|
||||||
|
no_color = partial(
|
||||||
|
Option,
|
||||||
|
'--no-color',
|
||||||
|
dest='no_color',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help="Suppress colored output",
|
||||||
|
)
|
||||||
|
|
||||||
|
version = partial(
|
||||||
|
Option,
|
||||||
|
'-V', '--version',
|
||||||
|
dest='version',
|
||||||
|
action='store_true',
|
||||||
|
help='Show version and exit.',
|
||||||
|
) # type: Any
|
||||||
|
|
||||||
|
quiet = partial(
|
||||||
|
Option,
|
||||||
|
'-q', '--quiet',
|
||||||
|
dest='quiet',
|
||||||
|
action='count',
|
||||||
|
default=0,
|
||||||
|
help=(
|
||||||
|
'Give less output. Option is additive, and can be used up to 3'
|
||||||
|
' times (corresponding to WARNING, ERROR, and CRITICAL logging'
|
||||||
|
' levels).'
|
||||||
|
),
|
||||||
|
) # type: Any
|
||||||
|
|
||||||
|
progress_bar = partial(
|
||||||
|
Option,
|
||||||
|
'--progress-bar',
|
||||||
|
dest='progress_bar',
|
||||||
|
type='choice',
|
||||||
|
choices=list(BAR_TYPES.keys()),
|
||||||
|
default='on',
|
||||||
|
help=(
|
||||||
|
'Specify type of progress to be displayed [' +
|
||||||
|
'|'.join(BAR_TYPES.keys()) + '] (default: %default)'
|
||||||
|
),
|
||||||
|
) # type: Any
|
||||||
|
|
||||||
|
log = partial(
|
||||||
|
Option,
|
||||||
|
"--log", "--log-file", "--local-log",
|
||||||
|
dest="log",
|
||||||
|
metavar="path",
|
||||||
|
help="Path to a verbose appending log."
|
||||||
|
) # type: Any
|
||||||
|
|
||||||
|
no_input = partial(
|
||||||
|
Option,
|
||||||
|
# Don't ask for input
|
||||||
|
'--no-input',
|
||||||
|
dest='no_input',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help=SUPPRESS_HELP
|
||||||
|
) # type: Any
|
||||||
|
|
||||||
|
proxy = partial(
|
||||||
|
Option,
|
||||||
|
'--proxy',
|
||||||
|
dest='proxy',
|
||||||
|
type='str',
|
||||||
|
default='',
|
||||||
|
help="Specify a proxy in the form [user:passwd@]proxy.server:port."
|
||||||
|
) # type: Any
|
||||||
|
|
||||||
|
retries = partial(
|
||||||
|
Option,
|
||||||
|
'--retries',
|
||||||
|
dest='retries',
|
||||||
|
type='int',
|
||||||
|
default=5,
|
||||||
|
help="Maximum number of retries each connection should attempt "
|
||||||
|
"(default %default times).",
|
||||||
|
) # type: Any
|
||||||
|
|
||||||
|
timeout = partial(
|
||||||
|
Option,
|
||||||
|
'--timeout', '--default-timeout',
|
||||||
|
metavar='sec',
|
||||||
|
dest='timeout',
|
||||||
|
type='float',
|
||||||
|
default=15,
|
||||||
|
help='Set the socket timeout (default %default seconds).',
|
||||||
|
) # type: Any
|
||||||
|
|
||||||
|
skip_requirements_regex = partial(
|
||||||
|
Option,
|
||||||
|
# A regex to be used to skip requirements
|
||||||
|
'--skip-requirements-regex',
|
||||||
|
dest='skip_requirements_regex',
|
||||||
|
type='str',
|
||||||
|
default='',
|
||||||
|
help=SUPPRESS_HELP,
|
||||||
|
) # type: Any
|
||||||
|
|
||||||
|
|
||||||
|
def exists_action():
|
||||||
|
return Option(
|
||||||
|
# Option when path already exist
|
||||||
|
'--exists-action',
|
||||||
|
dest='exists_action',
|
||||||
|
type='choice',
|
||||||
|
choices=['s', 'i', 'w', 'b', 'a'],
|
||||||
|
default=[],
|
||||||
|
action='append',
|
||||||
|
metavar='action',
|
||||||
|
help="Default action when a path already exists: "
|
||||||
|
"(s)witch, (i)gnore, (w)ipe, (b)ackup, (a)bort).",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
cert = partial(
|
||||||
|
Option,
|
||||||
|
'--cert',
|
||||||
|
dest='cert',
|
||||||
|
type='str',
|
||||||
|
metavar='path',
|
||||||
|
help="Path to alternate CA bundle.",
|
||||||
|
) # type: Any
|
||||||
|
|
||||||
|
client_cert = partial(
|
||||||
|
Option,
|
||||||
|
'--client-cert',
|
||||||
|
dest='client_cert',
|
||||||
|
type='str',
|
||||||
|
default=None,
|
||||||
|
metavar='path',
|
||||||
|
help="Path to SSL client certificate, a single file containing the "
|
||||||
|
"private key and the certificate in PEM format.",
|
||||||
|
) # type: Any
|
||||||
|
|
||||||
|
index_url = partial(
|
||||||
|
Option,
|
||||||
|
'-i', '--index-url', '--pypi-url',
|
||||||
|
dest='index_url',
|
||||||
|
metavar='URL',
|
||||||
|
default=PyPI.simple_url,
|
||||||
|
help="Base URL of Python Package Index (default %default). "
|
||||||
|
"This should point to a repository compliant with PEP 503 "
|
||||||
|
"(the simple repository API) or a local directory laid out "
|
||||||
|
"in the same format.",
|
||||||
|
) # type: Any
|
||||||
|
|
||||||
|
|
||||||
|
def extra_index_url():
|
||||||
|
return Option(
|
||||||
|
'--extra-index-url',
|
||||||
|
dest='extra_index_urls',
|
||||||
|
metavar='URL',
|
||||||
|
action='append',
|
||||||
|
default=[],
|
||||||
|
help="Extra URLs of package indexes to use in addition to "
|
||||||
|
"--index-url. Should follow the same rules as "
|
||||||
|
"--index-url.",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
no_index = partial(
|
||||||
|
Option,
|
||||||
|
'--no-index',
|
||||||
|
dest='no_index',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help='Ignore package index (only looking at --find-links URLs instead).',
|
||||||
|
) # type: Any
|
||||||
|
|
||||||
|
|
||||||
|
def find_links():
|
||||||
|
return Option(
|
||||||
|
'-f', '--find-links',
|
||||||
|
dest='find_links',
|
||||||
|
action='append',
|
||||||
|
default=[],
|
||||||
|
metavar='url',
|
||||||
|
help="If a url or path to an html file, then parse for links to "
|
||||||
|
"archives. If a local path or file:// url that's a directory, "
|
||||||
|
"then look for archives in the directory listing.",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def trusted_host():
|
||||||
|
return Option(
|
||||||
|
"--trusted-host",
|
||||||
|
dest="trusted_hosts",
|
||||||
|
action="append",
|
||||||
|
metavar="HOSTNAME",
|
||||||
|
default=[],
|
||||||
|
help="Mark this host as trusted, even though it does not have valid "
|
||||||
|
"or any HTTPS.",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Remove after 1.5
|
||||||
|
process_dependency_links = partial(
|
||||||
|
Option,
|
||||||
|
"--process-dependency-links",
|
||||||
|
dest="process_dependency_links",
|
||||||
|
action="store_true",
|
||||||
|
default=False,
|
||||||
|
help="Enable the processing of dependency links.",
|
||||||
|
) # type: Any
|
||||||
|
|
||||||
|
|
||||||
|
def constraints():
|
||||||
|
return Option(
|
||||||
|
'-c', '--constraint',
|
||||||
|
dest='constraints',
|
||||||
|
action='append',
|
||||||
|
default=[],
|
||||||
|
metavar='file',
|
||||||
|
help='Constrain versions using the given constraints file. '
|
||||||
|
'This option can be used multiple times.'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def requirements():
|
||||||
|
return Option(
|
||||||
|
'-r', '--requirement',
|
||||||
|
dest='requirements',
|
||||||
|
action='append',
|
||||||
|
default=[],
|
||||||
|
metavar='file',
|
||||||
|
help='Install from the given requirements file. '
|
||||||
|
'This option can be used multiple times.'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def editable():
|
||||||
|
return Option(
|
||||||
|
'-e', '--editable',
|
||||||
|
dest='editables',
|
||||||
|
action='append',
|
||||||
|
default=[],
|
||||||
|
metavar='path/url',
|
||||||
|
help=('Install a project in editable mode (i.e. setuptools '
|
||||||
|
'"develop mode") from a local project path or a VCS url.'),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
src = partial(
|
||||||
|
Option,
|
||||||
|
'--src', '--source', '--source-dir', '--source-directory',
|
||||||
|
dest='src_dir',
|
||||||
|
metavar='dir',
|
||||||
|
default=src_prefix,
|
||||||
|
help='Directory to check out editable projects into. '
|
||||||
|
'The default in a virtualenv is "<venv path>/src". '
|
||||||
|
'The default for global installs is "<current dir>/src".'
|
||||||
|
) # type: Any
|
||||||
|
|
||||||
|
|
||||||
|
def _get_format_control(values, option):
|
||||||
|
"""Get a format_control object."""
|
||||||
|
return getattr(values, option.dest)
|
||||||
|
|
||||||
|
|
||||||
|
def _handle_no_binary(option, opt_str, value, parser):
|
||||||
|
existing = getattr(parser.values, option.dest)
|
||||||
|
fmt_ctl_handle_mutual_exclude(
|
||||||
|
value, existing.no_binary, existing.only_binary,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _handle_only_binary(option, opt_str, value, parser):
|
||||||
|
existing = getattr(parser.values, option.dest)
|
||||||
|
fmt_ctl_handle_mutual_exclude(
|
||||||
|
value, existing.only_binary, existing.no_binary,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def no_binary():
|
||||||
|
return Option(
|
||||||
|
"--no-binary", dest="format_control", action="callback",
|
||||||
|
callback=_handle_no_binary, type="str",
|
||||||
|
default=FormatControl(set(), set()),
|
||||||
|
help="Do not use binary packages. Can be supplied multiple times, and "
|
||||||
|
"each time adds to the existing value. Accepts either :all: to "
|
||||||
|
"disable all binary packages, :none: to empty the set, or one or "
|
||||||
|
"more package names with commas between them. Note that some "
|
||||||
|
"packages are tricky to compile and may fail to install when "
|
||||||
|
"this option is used on them.",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def only_binary():
|
||||||
|
return Option(
|
||||||
|
"--only-binary", dest="format_control", action="callback",
|
||||||
|
callback=_handle_only_binary, type="str",
|
||||||
|
default=FormatControl(set(), set()),
|
||||||
|
help="Do not use source packages. Can be supplied multiple times, and "
|
||||||
|
"each time adds to the existing value. Accepts either :all: to "
|
||||||
|
"disable all source packages, :none: to empty the set, or one or "
|
||||||
|
"more package names with commas between them. Packages without "
|
||||||
|
"binary distributions will fail to install when this option is "
|
||||||
|
"used on them.",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
cache_dir = partial(
|
||||||
|
Option,
|
||||||
|
"--cache-dir",
|
||||||
|
dest="cache_dir",
|
||||||
|
default=USER_CACHE_DIR,
|
||||||
|
metavar="dir",
|
||||||
|
help="Store the cache data in <dir>."
|
||||||
|
)
|
||||||
|
|
||||||
|
no_cache = partial(
|
||||||
|
Option,
|
||||||
|
"--no-cache-dir",
|
||||||
|
dest="cache_dir",
|
||||||
|
action="store_false",
|
||||||
|
help="Disable the cache.",
|
||||||
|
)
|
||||||
|
|
||||||
|
no_deps = partial(
|
||||||
|
Option,
|
||||||
|
'--no-deps', '--no-dependencies',
|
||||||
|
dest='ignore_dependencies',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help="Don't install package dependencies.",
|
||||||
|
) # type: Any
|
||||||
|
|
||||||
|
build_dir = partial(
|
||||||
|
Option,
|
||||||
|
'-b', '--build', '--build-dir', '--build-directory',
|
||||||
|
dest='build_dir',
|
||||||
|
metavar='dir',
|
||||||
|
help='Directory to unpack packages into and build in. Note that '
|
||||||
|
'an initial build still takes place in a temporary directory. '
|
||||||
|
'The location of temporary directories can be controlled by setting '
|
||||||
|
'the TMPDIR environment variable (TEMP on Windows) appropriately. '
|
||||||
|
'When passed, build directories are not cleaned in case of failures.'
|
||||||
|
) # type: Any
|
||||||
|
|
||||||
|
ignore_requires_python = partial(
|
||||||
|
Option,
|
||||||
|
'--ignore-requires-python',
|
||||||
|
dest='ignore_requires_python',
|
||||||
|
action='store_true',
|
||||||
|
help='Ignore the Requires-Python information.'
|
||||||
|
) # type: Any
|
||||||
|
|
||||||
|
no_build_isolation = partial(
|
||||||
|
Option,
|
||||||
|
'--no-build-isolation',
|
||||||
|
dest='build_isolation',
|
||||||
|
action='store_false',
|
||||||
|
default=True,
|
||||||
|
help='Disable isolation when building a modern source distribution. '
|
||||||
|
'Build dependencies specified by PEP 518 must be already installed '
|
||||||
|
'if this option is used.'
|
||||||
|
) # type: Any
|
||||||
|
|
||||||
|
install_options = partial(
|
||||||
|
Option,
|
||||||
|
'--install-option',
|
||||||
|
dest='install_options',
|
||||||
|
action='append',
|
||||||
|
metavar='options',
|
||||||
|
help="Extra arguments to be supplied to the setup.py install "
|
||||||
|
"command (use like --install-option=\"--install-scripts=/usr/local/"
|
||||||
|
"bin\"). Use multiple --install-option options to pass multiple "
|
||||||
|
"options to setup.py install. If you are using an option with a "
|
||||||
|
"directory path, be sure to use absolute path.",
|
||||||
|
) # type: Any
|
||||||
|
|
||||||
|
global_options = partial(
|
||||||
|
Option,
|
||||||
|
'--global-option',
|
||||||
|
dest='global_options',
|
||||||
|
action='append',
|
||||||
|
metavar='options',
|
||||||
|
help="Extra global options to be supplied to the setup.py "
|
||||||
|
"call before the install command.",
|
||||||
|
) # type: Any
|
||||||
|
|
||||||
|
no_clean = partial(
|
||||||
|
Option,
|
||||||
|
'--no-clean',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help="Don't clean up build directories)."
|
||||||
|
) # type: Any
|
||||||
|
|
||||||
|
pre = partial(
|
||||||
|
Option,
|
||||||
|
'--pre',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help="Include pre-release and development versions. By default, "
|
||||||
|
"pip only finds stable versions.",
|
||||||
|
) # type: Any
|
||||||
|
|
||||||
|
disable_pip_version_check = partial(
|
||||||
|
Option,
|
||||||
|
"--disable-pip-version-check",
|
||||||
|
dest="disable_pip_version_check",
|
||||||
|
action="store_true",
|
||||||
|
default=False,
|
||||||
|
help="Don't periodically check PyPI to determine whether a new version "
|
||||||
|
"of pip is available for download. Implied with --no-index.",
|
||||||
|
) # type: Any
|
||||||
|
|
||||||
|
|
||||||
|
# Deprecated, Remove later
|
||||||
|
always_unzip = partial(
|
||||||
|
Option,
|
||||||
|
'-Z', '--always-unzip',
|
||||||
|
dest='always_unzip',
|
||||||
|
action='store_true',
|
||||||
|
help=SUPPRESS_HELP,
|
||||||
|
) # type: Any
|
||||||
|
|
||||||
|
|
||||||
|
def _merge_hash(option, opt_str, value, parser):
|
||||||
|
"""Given a value spelled "algo:digest", append the digest to a list
|
||||||
|
pointed to in a dict by the algo name."""
|
||||||
|
if not parser.values.hashes:
|
||||||
|
parser.values.hashes = {}
|
||||||
|
try:
|
||||||
|
algo, digest = value.split(':', 1)
|
||||||
|
except ValueError:
|
||||||
|
parser.error('Arguments to %s must be a hash name '
|
||||||
|
'followed by a value, like --hash=sha256:abcde...' %
|
||||||
|
opt_str)
|
||||||
|
if algo not in STRONG_HASHES:
|
||||||
|
parser.error('Allowed hash algorithms for %s are %s.' %
|
||||||
|
(opt_str, ', '.join(STRONG_HASHES)))
|
||||||
|
parser.values.hashes.setdefault(algo, []).append(digest)
|
||||||
|
|
||||||
|
|
||||||
|
hash = partial(
|
||||||
|
Option,
|
||||||
|
'--hash',
|
||||||
|
# Hash values eventually end up in InstallRequirement.hashes due to
|
||||||
|
# __dict__ copying in process_line().
|
||||||
|
dest='hashes',
|
||||||
|
action='callback',
|
||||||
|
callback=_merge_hash,
|
||||||
|
type='string',
|
||||||
|
help="Verify that the package's archive matches this "
|
||||||
|
'hash before installing. Example: --hash=sha256:abcdef...',
|
||||||
|
) # type: Any
|
||||||
|
|
||||||
|
|
||||||
|
require_hashes = partial(
|
||||||
|
Option,
|
||||||
|
'--require-hashes',
|
||||||
|
dest='require_hashes',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help='Require a hash to check each requirement against, for '
|
||||||
|
'repeatable installs. This option is implied when any package in a '
|
||||||
|
'requirements file has a --hash option.',
|
||||||
|
) # type: Any
|
||||||
|
|
||||||
|
|
||||||
|
##########
|
||||||
|
# groups #
|
||||||
|
##########
|
||||||
|
|
||||||
|
general_group = {
|
||||||
|
'name': 'General Options',
|
||||||
|
'options': [
|
||||||
|
help_,
|
||||||
|
isolated_mode,
|
||||||
|
require_virtualenv,
|
||||||
|
verbose,
|
||||||
|
version,
|
||||||
|
quiet,
|
||||||
|
log,
|
||||||
|
no_input,
|
||||||
|
proxy,
|
||||||
|
retries,
|
||||||
|
timeout,
|
||||||
|
skip_requirements_regex,
|
||||||
|
exists_action,
|
||||||
|
trusted_host,
|
||||||
|
cert,
|
||||||
|
client_cert,
|
||||||
|
cache_dir,
|
||||||
|
no_cache,
|
||||||
|
disable_pip_version_check,
|
||||||
|
no_color,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
index_group = {
|
||||||
|
'name': 'Package Index Options',
|
||||||
|
'options': [
|
||||||
|
index_url,
|
||||||
|
extra_index_url,
|
||||||
|
no_index,
|
||||||
|
find_links,
|
||||||
|
process_dependency_links,
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -0,0 +1,79 @@
|
|||||||
|
"""
|
||||||
|
Package containing all pip commands
|
||||||
|
"""
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
from pip._internal.commands.completion import CompletionCommand
|
||||||
|
from pip._internal.commands.configuration import ConfigurationCommand
|
||||||
|
from pip._internal.commands.download import DownloadCommand
|
||||||
|
from pip._internal.commands.freeze import FreezeCommand
|
||||||
|
from pip._internal.commands.hash import HashCommand
|
||||||
|
from pip._internal.commands.help import HelpCommand
|
||||||
|
from pip._internal.commands.list import ListCommand
|
||||||
|
from pip._internal.commands.check import CheckCommand
|
||||||
|
from pip._internal.commands.search import SearchCommand
|
||||||
|
from pip._internal.commands.show import ShowCommand
|
||||||
|
from pip._internal.commands.install import InstallCommand
|
||||||
|
from pip._internal.commands.uninstall import UninstallCommand
|
||||||
|
from pip._internal.commands.wheel import WheelCommand
|
||||||
|
|
||||||
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
||||||
|
|
||||||
|
if MYPY_CHECK_RUNNING:
|
||||||
|
from typing import List, Type
|
||||||
|
from pip._internal.basecommand import Command
|
||||||
|
|
||||||
|
commands_order = [
|
||||||
|
InstallCommand,
|
||||||
|
DownloadCommand,
|
||||||
|
UninstallCommand,
|
||||||
|
FreezeCommand,
|
||||||
|
ListCommand,
|
||||||
|
ShowCommand,
|
||||||
|
CheckCommand,
|
||||||
|
ConfigurationCommand,
|
||||||
|
SearchCommand,
|
||||||
|
WheelCommand,
|
||||||
|
HashCommand,
|
||||||
|
CompletionCommand,
|
||||||
|
HelpCommand,
|
||||||
|
] # type: List[Type[Command]]
|
||||||
|
|
||||||
|
commands_dict = {c.name: c for c in commands_order}
|
||||||
|
|
||||||
|
|
||||||
|
def get_summaries(ordered=True):
|
||||||
|
"""Yields sorted (command name, command summary) tuples."""
|
||||||
|
|
||||||
|
if ordered:
|
||||||
|
cmditems = _sort_commands(commands_dict, commands_order)
|
||||||
|
else:
|
||||||
|
cmditems = commands_dict.items()
|
||||||
|
|
||||||
|
for name, command_class in cmditems:
|
||||||
|
yield (name, command_class.summary)
|
||||||
|
|
||||||
|
|
||||||
|
def get_similar_commands(name):
|
||||||
|
"""Command name auto-correct."""
|
||||||
|
from difflib import get_close_matches
|
||||||
|
|
||||||
|
name = name.lower()
|
||||||
|
|
||||||
|
close_commands = get_close_matches(name, commands_dict.keys())
|
||||||
|
|
||||||
|
if close_commands:
|
||||||
|
return close_commands[0]
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _sort_commands(cmddict, order):
|
||||||
|
def keyfn(key):
|
||||||
|
try:
|
||||||
|
return order.index(key[1])
|
||||||
|
except ValueError:
|
||||||
|
# unordered items should come last
|
||||||
|
return 0xff
|
||||||
|
|
||||||
|
return sorted(cmddict.items(), key=keyfn)
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
from pip._internal.basecommand import Command
|
||||||
|
from pip._internal.operations.check import (
|
||||||
|
check_package_set, create_package_set_from_installed,
|
||||||
|
)
|
||||||
|
from pip._internal.utils.misc import get_installed_distributions
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class CheckCommand(Command):
|
||||||
|
"""Verify installed packages have compatible dependencies."""
|
||||||
|
name = 'check'
|
||||||
|
usage = """
|
||||||
|
%prog [options]"""
|
||||||
|
summary = 'Verify installed packages have compatible dependencies.'
|
||||||
|
|
||||||
|
def run(self, options, args):
|
||||||
|
package_set = create_package_set_from_installed()
|
||||||
|
missing, conflicting = check_package_set(package_set)
|
||||||
|
|
||||||
|
for project_name in missing:
|
||||||
|
version = package_set[project_name].version
|
||||||
|
for dependency in missing[project_name]:
|
||||||
|
logger.info(
|
||||||
|
"%s %s requires %s, which is not installed.",
|
||||||
|
project_name, version, dependency[0],
|
||||||
|
)
|
||||||
|
|
||||||
|
for project_name in conflicting:
|
||||||
|
version = package_set[project_name].version
|
||||||
|
for dep_name, dep_version, req in conflicting[project_name]:
|
||||||
|
logger.info(
|
||||||
|
"%s %s has requirement %s, but you have %s %s.",
|
||||||
|
project_name, version, req, dep_name, dep_version,
|
||||||
|
)
|
||||||
|
|
||||||
|
if missing or conflicting:
|
||||||
|
return 1
|
||||||
|
else:
|
||||||
|
logger.info("No broken requirements found.")
|
||||||
@ -0,0 +1,94 @@
|
|||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import textwrap
|
||||||
|
|
||||||
|
from pip._internal.basecommand import Command
|
||||||
|
from pip._internal.utils.misc import get_prog
|
||||||
|
|
||||||
|
BASE_COMPLETION = """
|
||||||
|
# pip %(shell)s completion start%(script)s# pip %(shell)s completion end
|
||||||
|
"""
|
||||||
|
|
||||||
|
COMPLETION_SCRIPTS = {
|
||||||
|
'bash': """
|
||||||
|
_pip_completion()
|
||||||
|
{
|
||||||
|
COMPREPLY=( $( COMP_WORDS="${COMP_WORDS[*]}" \\
|
||||||
|
COMP_CWORD=$COMP_CWORD \\
|
||||||
|
PIP_AUTO_COMPLETE=1 $1 ) )
|
||||||
|
}
|
||||||
|
complete -o default -F _pip_completion %(prog)s
|
||||||
|
""",
|
||||||
|
'zsh': """
|
||||||
|
function _pip_completion {
|
||||||
|
local words cword
|
||||||
|
read -Ac words
|
||||||
|
read -cn cword
|
||||||
|
reply=( $( COMP_WORDS="$words[*]" \\
|
||||||
|
COMP_CWORD=$(( cword-1 )) \\
|
||||||
|
PIP_AUTO_COMPLETE=1 $words[1] ) )
|
||||||
|
}
|
||||||
|
compctl -K _pip_completion %(prog)s
|
||||||
|
""",
|
||||||
|
'fish': """
|
||||||
|
function __fish_complete_pip
|
||||||
|
set -lx COMP_WORDS (commandline -o) ""
|
||||||
|
set -lx COMP_CWORD ( \\
|
||||||
|
math (contains -i -- (commandline -t) $COMP_WORDS)-1 \\
|
||||||
|
)
|
||||||
|
set -lx PIP_AUTO_COMPLETE 1
|
||||||
|
string split \\ -- (eval $COMP_WORDS[1])
|
||||||
|
end
|
||||||
|
complete -fa "(__fish_complete_pip)" -c %(prog)s
|
||||||
|
""",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class CompletionCommand(Command):
|
||||||
|
"""A helper command to be used for command completion."""
|
||||||
|
name = 'completion'
|
||||||
|
summary = 'A helper command used for command completion.'
|
||||||
|
ignore_require_venv = True
|
||||||
|
|
||||||
|
def __init__(self, *args, **kw):
|
||||||
|
super(CompletionCommand, self).__init__(*args, **kw)
|
||||||
|
|
||||||
|
cmd_opts = self.cmd_opts
|
||||||
|
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'--bash', '-b',
|
||||||
|
action='store_const',
|
||||||
|
const='bash',
|
||||||
|
dest='shell',
|
||||||
|
help='Emit completion code for bash')
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'--zsh', '-z',
|
||||||
|
action='store_const',
|
||||||
|
const='zsh',
|
||||||
|
dest='shell',
|
||||||
|
help='Emit completion code for zsh')
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'--fish', '-f',
|
||||||
|
action='store_const',
|
||||||
|
const='fish',
|
||||||
|
dest='shell',
|
||||||
|
help='Emit completion code for fish')
|
||||||
|
|
||||||
|
self.parser.insert_option_group(0, cmd_opts)
|
||||||
|
|
||||||
|
def run(self, options, args):
|
||||||
|
"""Prints the completion code of the given shell"""
|
||||||
|
shells = COMPLETION_SCRIPTS.keys()
|
||||||
|
shell_options = ['--' + shell for shell in sorted(shells)]
|
||||||
|
if options.shell in shells:
|
||||||
|
script = textwrap.dedent(
|
||||||
|
COMPLETION_SCRIPTS.get(options.shell, '') % {
|
||||||
|
'prog': get_prog(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
print(BASE_COMPLETION % {'script': script, 'shell': options.shell})
|
||||||
|
else:
|
||||||
|
sys.stderr.write(
|
||||||
|
'ERROR: You must pass %s\n' % ' or '.join(shell_options)
|
||||||
|
)
|
||||||
@ -0,0 +1,227 @@
|
|||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
from pip._internal.basecommand import Command
|
||||||
|
from pip._internal.configuration import Configuration, kinds
|
||||||
|
from pip._internal.exceptions import PipError
|
||||||
|
from pip._internal.locations import venv_config_file
|
||||||
|
from pip._internal.status_codes import ERROR, SUCCESS
|
||||||
|
from pip._internal.utils.misc import get_prog
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigurationCommand(Command):
|
||||||
|
"""Manage local and global configuration.
|
||||||
|
|
||||||
|
Subcommands:
|
||||||
|
|
||||||
|
list: List the active configuration (or from the file specified)
|
||||||
|
edit: Edit the configuration file in an editor
|
||||||
|
get: Get the value associated with name
|
||||||
|
set: Set the name=value
|
||||||
|
unset: Unset the value associated with name
|
||||||
|
|
||||||
|
If none of --user, --global and --venv are passed, a virtual
|
||||||
|
environment configuration file is used if one is active and the file
|
||||||
|
exists. Otherwise, all modifications happen on the to the user file by
|
||||||
|
default.
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = 'config'
|
||||||
|
usage = """
|
||||||
|
%prog [<file-option>] list
|
||||||
|
%prog [<file-option>] [--editor <editor-path>] edit
|
||||||
|
|
||||||
|
%prog [<file-option>] get name
|
||||||
|
%prog [<file-option>] set name value
|
||||||
|
%prog [<file-option>] unset name
|
||||||
|
"""
|
||||||
|
|
||||||
|
summary = "Manage local and global configuration."
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(ConfigurationCommand, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
self.configuration = None
|
||||||
|
|
||||||
|
self.cmd_opts.add_option(
|
||||||
|
'--editor',
|
||||||
|
dest='editor',
|
||||||
|
action='store',
|
||||||
|
default=None,
|
||||||
|
help=(
|
||||||
|
'Editor to use to edit the file. Uses VISUAL or EDITOR '
|
||||||
|
'environment variables if not provided.'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.cmd_opts.add_option(
|
||||||
|
'--global',
|
||||||
|
dest='global_file',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help='Use the system-wide configuration file only'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.cmd_opts.add_option(
|
||||||
|
'--user',
|
||||||
|
dest='user_file',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help='Use the user configuration file only'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.cmd_opts.add_option(
|
||||||
|
'--venv',
|
||||||
|
dest='venv_file',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help='Use the virtualenv configuration file only'
|
||||||
|
)
|
||||||
|
|
||||||
|
self.parser.insert_option_group(0, self.cmd_opts)
|
||||||
|
|
||||||
|
def run(self, options, args):
|
||||||
|
handlers = {
|
||||||
|
"list": self.list_values,
|
||||||
|
"edit": self.open_in_editor,
|
||||||
|
"get": self.get_name,
|
||||||
|
"set": self.set_name_value,
|
||||||
|
"unset": self.unset_name
|
||||||
|
}
|
||||||
|
|
||||||
|
# Determine action
|
||||||
|
if not args or args[0] not in handlers:
|
||||||
|
logger.error("Need an action ({}) to perform.".format(
|
||||||
|
", ".join(sorted(handlers)))
|
||||||
|
)
|
||||||
|
return ERROR
|
||||||
|
|
||||||
|
action = args[0]
|
||||||
|
|
||||||
|
# Determine which configuration files are to be loaded
|
||||||
|
# Depends on whether the command is modifying.
|
||||||
|
try:
|
||||||
|
load_only = self._determine_file(
|
||||||
|
options, need_value=(action in ["get", "set", "unset", "edit"])
|
||||||
|
)
|
||||||
|
except PipError as e:
|
||||||
|
logger.error(e.args[0])
|
||||||
|
return ERROR
|
||||||
|
|
||||||
|
# Load a new configuration
|
||||||
|
self.configuration = Configuration(
|
||||||
|
isolated=options.isolated_mode, load_only=load_only
|
||||||
|
)
|
||||||
|
self.configuration.load()
|
||||||
|
|
||||||
|
# Error handling happens here, not in the action-handlers.
|
||||||
|
try:
|
||||||
|
handlers[action](options, args[1:])
|
||||||
|
except PipError as e:
|
||||||
|
logger.error(e.args[0])
|
||||||
|
return ERROR
|
||||||
|
|
||||||
|
return SUCCESS
|
||||||
|
|
||||||
|
def _determine_file(self, options, need_value):
|
||||||
|
file_options = {
|
||||||
|
kinds.USER: options.user_file,
|
||||||
|
kinds.GLOBAL: options.global_file,
|
||||||
|
kinds.VENV: options.venv_file
|
||||||
|
}
|
||||||
|
|
||||||
|
if sum(file_options.values()) == 0:
|
||||||
|
if not need_value:
|
||||||
|
return None
|
||||||
|
# Default to user, unless there's a virtualenv file.
|
||||||
|
elif os.path.exists(venv_config_file):
|
||||||
|
return kinds.VENV
|
||||||
|
else:
|
||||||
|
return kinds.USER
|
||||||
|
elif sum(file_options.values()) == 1:
|
||||||
|
# There's probably a better expression for this.
|
||||||
|
return [key for key in file_options if file_options[key]][0]
|
||||||
|
|
||||||
|
raise PipError(
|
||||||
|
"Need exactly one file to operate upon "
|
||||||
|
"(--user, --venv, --global) to perform."
|
||||||
|
)
|
||||||
|
|
||||||
|
def list_values(self, options, args):
|
||||||
|
self._get_n_args(args, "list", n=0)
|
||||||
|
|
||||||
|
for key, value in sorted(self.configuration.items()):
|
||||||
|
logger.info("%s=%r", key, value)
|
||||||
|
|
||||||
|
def get_name(self, options, args):
|
||||||
|
key = self._get_n_args(args, "get [name]", n=1)
|
||||||
|
value = self.configuration.get_value(key)
|
||||||
|
|
||||||
|
logger.info("%s", value)
|
||||||
|
|
||||||
|
def set_name_value(self, options, args):
|
||||||
|
key, value = self._get_n_args(args, "set [name] [value]", n=2)
|
||||||
|
self.configuration.set_value(key, value)
|
||||||
|
|
||||||
|
self._save_configuration()
|
||||||
|
|
||||||
|
def unset_name(self, options, args):
|
||||||
|
key = self._get_n_args(args, "unset [name]", n=1)
|
||||||
|
self.configuration.unset_value(key)
|
||||||
|
|
||||||
|
self._save_configuration()
|
||||||
|
|
||||||
|
def open_in_editor(self, options, args):
|
||||||
|
editor = self._determine_editor(options)
|
||||||
|
|
||||||
|
fname = self.configuration.get_file_to_edit()
|
||||||
|
if fname is None:
|
||||||
|
raise PipError("Could not determine appropriate file.")
|
||||||
|
|
||||||
|
try:
|
||||||
|
subprocess.check_call([editor, fname])
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
raise PipError(
|
||||||
|
"Editor Subprocess exited with exit code {}"
|
||||||
|
.format(e.returncode)
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_n_args(self, args, example, n):
|
||||||
|
"""Helper to make sure the command got the right number of arguments
|
||||||
|
"""
|
||||||
|
if len(args) != n:
|
||||||
|
msg = (
|
||||||
|
'Got unexpected number of arguments, expected {}. '
|
||||||
|
'(example: "{} config {}")'
|
||||||
|
).format(n, get_prog(), example)
|
||||||
|
raise PipError(msg)
|
||||||
|
|
||||||
|
if n == 1:
|
||||||
|
return args[0]
|
||||||
|
else:
|
||||||
|
return args
|
||||||
|
|
||||||
|
def _save_configuration(self):
|
||||||
|
# We successfully ran a modifying command. Need to save the
|
||||||
|
# configuration.
|
||||||
|
try:
|
||||||
|
self.configuration.save()
|
||||||
|
except Exception:
|
||||||
|
logger.error(
|
||||||
|
"Unable to save configuration. Please report this as a bug.",
|
||||||
|
exc_info=1
|
||||||
|
)
|
||||||
|
raise PipError("Internal Error.")
|
||||||
|
|
||||||
|
def _determine_editor(self, options):
|
||||||
|
if options.editor is not None:
|
||||||
|
return options.editor
|
||||||
|
elif "VISUAL" in os.environ:
|
||||||
|
return os.environ["VISUAL"]
|
||||||
|
elif "EDITOR" in os.environ:
|
||||||
|
return os.environ["EDITOR"]
|
||||||
|
else:
|
||||||
|
raise PipError("Could not determine editor to use.")
|
||||||
@ -0,0 +1,233 @@
|
|||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
from pip._internal import cmdoptions
|
||||||
|
from pip._internal.basecommand import RequirementCommand
|
||||||
|
from pip._internal.exceptions import CommandError
|
||||||
|
from pip._internal.index import FormatControl
|
||||||
|
from pip._internal.operations.prepare import RequirementPreparer
|
||||||
|
from pip._internal.req import RequirementSet
|
||||||
|
from pip._internal.resolve import Resolver
|
||||||
|
from pip._internal.utils.filesystem import check_path_owner
|
||||||
|
from pip._internal.utils.misc import ensure_dir, normalize_path
|
||||||
|
from pip._internal.utils.temp_dir import TempDirectory
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class DownloadCommand(RequirementCommand):
|
||||||
|
"""
|
||||||
|
Download packages from:
|
||||||
|
|
||||||
|
- PyPI (and other indexes) using requirement specifiers.
|
||||||
|
- VCS project urls.
|
||||||
|
- Local project directories.
|
||||||
|
- Local or remote source archives.
|
||||||
|
|
||||||
|
pip also supports downloading from "requirements files", which provide
|
||||||
|
an easy way to specify a whole environment to be downloaded.
|
||||||
|
"""
|
||||||
|
name = 'download'
|
||||||
|
|
||||||
|
usage = """
|
||||||
|
%prog [options] <requirement specifier> [package-index-options] ...
|
||||||
|
%prog [options] -r <requirements file> [package-index-options] ...
|
||||||
|
%prog [options] <vcs project url> ...
|
||||||
|
%prog [options] <local project path> ...
|
||||||
|
%prog [options] <archive url/path> ..."""
|
||||||
|
|
||||||
|
summary = 'Download packages.'
|
||||||
|
|
||||||
|
def __init__(self, *args, **kw):
|
||||||
|
super(DownloadCommand, self).__init__(*args, **kw)
|
||||||
|
|
||||||
|
cmd_opts = self.cmd_opts
|
||||||
|
|
||||||
|
cmd_opts.add_option(cmdoptions.constraints())
|
||||||
|
cmd_opts.add_option(cmdoptions.requirements())
|
||||||
|
cmd_opts.add_option(cmdoptions.build_dir())
|
||||||
|
cmd_opts.add_option(cmdoptions.no_deps())
|
||||||
|
cmd_opts.add_option(cmdoptions.global_options())
|
||||||
|
cmd_opts.add_option(cmdoptions.no_binary())
|
||||||
|
cmd_opts.add_option(cmdoptions.only_binary())
|
||||||
|
cmd_opts.add_option(cmdoptions.src())
|
||||||
|
cmd_opts.add_option(cmdoptions.pre())
|
||||||
|
cmd_opts.add_option(cmdoptions.no_clean())
|
||||||
|
cmd_opts.add_option(cmdoptions.require_hashes())
|
||||||
|
cmd_opts.add_option(cmdoptions.progress_bar())
|
||||||
|
cmd_opts.add_option(cmdoptions.no_build_isolation())
|
||||||
|
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'-d', '--dest', '--destination-dir', '--destination-directory',
|
||||||
|
dest='download_dir',
|
||||||
|
metavar='dir',
|
||||||
|
default=os.curdir,
|
||||||
|
help=("Download packages into <dir>."),
|
||||||
|
)
|
||||||
|
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'--platform',
|
||||||
|
dest='platform',
|
||||||
|
metavar='platform',
|
||||||
|
default=None,
|
||||||
|
help=("Only download wheels compatible with <platform>. "
|
||||||
|
"Defaults to the platform of the running system."),
|
||||||
|
)
|
||||||
|
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'--python-version',
|
||||||
|
dest='python_version',
|
||||||
|
metavar='python_version',
|
||||||
|
default=None,
|
||||||
|
help=("Only download wheels compatible with Python "
|
||||||
|
"interpreter version <version>. If not specified, then the "
|
||||||
|
"current system interpreter minor version is used. A major "
|
||||||
|
"version (e.g. '2') can be specified to match all "
|
||||||
|
"minor revs of that major version. A minor version "
|
||||||
|
"(e.g. '34') can also be specified."),
|
||||||
|
)
|
||||||
|
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'--implementation',
|
||||||
|
dest='implementation',
|
||||||
|
metavar='implementation',
|
||||||
|
default=None,
|
||||||
|
help=("Only download wheels compatible with Python "
|
||||||
|
"implementation <implementation>, e.g. 'pp', 'jy', 'cp', "
|
||||||
|
" or 'ip'. If not specified, then the current "
|
||||||
|
"interpreter implementation is used. Use 'py' to force "
|
||||||
|
"implementation-agnostic wheels."),
|
||||||
|
)
|
||||||
|
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'--abi',
|
||||||
|
dest='abi',
|
||||||
|
metavar='abi',
|
||||||
|
default=None,
|
||||||
|
help=("Only download wheels compatible with Python "
|
||||||
|
"abi <abi>, e.g. 'pypy_41'. If not specified, then the "
|
||||||
|
"current interpreter abi tag is used. Generally "
|
||||||
|
"you will need to specify --implementation, "
|
||||||
|
"--platform, and --python-version when using "
|
||||||
|
"this option."),
|
||||||
|
)
|
||||||
|
|
||||||
|
index_opts = cmdoptions.make_option_group(
|
||||||
|
cmdoptions.index_group,
|
||||||
|
self.parser,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.parser.insert_option_group(0, index_opts)
|
||||||
|
self.parser.insert_option_group(0, cmd_opts)
|
||||||
|
|
||||||
|
def run(self, options, args):
|
||||||
|
options.ignore_installed = True
|
||||||
|
# editable doesn't really make sense for `pip download`, but the bowels
|
||||||
|
# of the RequirementSet code require that property.
|
||||||
|
options.editables = []
|
||||||
|
|
||||||
|
if options.python_version:
|
||||||
|
python_versions = [options.python_version]
|
||||||
|
else:
|
||||||
|
python_versions = None
|
||||||
|
|
||||||
|
dist_restriction_set = any([
|
||||||
|
options.python_version,
|
||||||
|
options.platform,
|
||||||
|
options.abi,
|
||||||
|
options.implementation,
|
||||||
|
])
|
||||||
|
binary_only = FormatControl(set(), {':all:'})
|
||||||
|
no_sdist_dependencies = (
|
||||||
|
options.format_control != binary_only and
|
||||||
|
not options.ignore_dependencies
|
||||||
|
)
|
||||||
|
if dist_restriction_set and no_sdist_dependencies:
|
||||||
|
raise CommandError(
|
||||||
|
"When restricting platform and interpreter constraints using "
|
||||||
|
"--python-version, --platform, --abi, or --implementation, "
|
||||||
|
"either --no-deps must be set, or --only-binary=:all: must be "
|
||||||
|
"set and --no-binary must not be set (or must be set to "
|
||||||
|
":none:)."
|
||||||
|
)
|
||||||
|
|
||||||
|
options.src_dir = os.path.abspath(options.src_dir)
|
||||||
|
options.download_dir = normalize_path(options.download_dir)
|
||||||
|
|
||||||
|
ensure_dir(options.download_dir)
|
||||||
|
|
||||||
|
with self._build_session(options) as session:
|
||||||
|
finder = self._build_package_finder(
|
||||||
|
options=options,
|
||||||
|
session=session,
|
||||||
|
platform=options.platform,
|
||||||
|
python_versions=python_versions,
|
||||||
|
abi=options.abi,
|
||||||
|
implementation=options.implementation,
|
||||||
|
)
|
||||||
|
build_delete = (not (options.no_clean or options.build_dir))
|
||||||
|
if options.cache_dir and not check_path_owner(options.cache_dir):
|
||||||
|
logger.warning(
|
||||||
|
"The directory '%s' or its parent directory is not owned "
|
||||||
|
"by the current user and caching wheels has been "
|
||||||
|
"disabled. check the permissions and owner of that "
|
||||||
|
"directory. If executing pip with sudo, you may want "
|
||||||
|
"sudo's -H flag.",
|
||||||
|
options.cache_dir,
|
||||||
|
)
|
||||||
|
options.cache_dir = None
|
||||||
|
|
||||||
|
with TempDirectory(
|
||||||
|
options.build_dir, delete=build_delete, kind="download"
|
||||||
|
) as directory:
|
||||||
|
|
||||||
|
requirement_set = RequirementSet(
|
||||||
|
require_hashes=options.require_hashes,
|
||||||
|
)
|
||||||
|
self.populate_requirement_set(
|
||||||
|
requirement_set,
|
||||||
|
args,
|
||||||
|
options,
|
||||||
|
finder,
|
||||||
|
session,
|
||||||
|
self.name,
|
||||||
|
None
|
||||||
|
)
|
||||||
|
|
||||||
|
preparer = RequirementPreparer(
|
||||||
|
build_dir=directory.path,
|
||||||
|
src_dir=options.src_dir,
|
||||||
|
download_dir=options.download_dir,
|
||||||
|
wheel_download_dir=None,
|
||||||
|
progress_bar=options.progress_bar,
|
||||||
|
build_isolation=options.build_isolation,
|
||||||
|
)
|
||||||
|
|
||||||
|
resolver = Resolver(
|
||||||
|
preparer=preparer,
|
||||||
|
finder=finder,
|
||||||
|
session=session,
|
||||||
|
wheel_cache=None,
|
||||||
|
use_user_site=False,
|
||||||
|
upgrade_strategy="to-satisfy-only",
|
||||||
|
force_reinstall=False,
|
||||||
|
ignore_dependencies=options.ignore_dependencies,
|
||||||
|
ignore_requires_python=False,
|
||||||
|
ignore_installed=True,
|
||||||
|
isolated=options.isolated_mode,
|
||||||
|
)
|
||||||
|
resolver.resolve(requirement_set)
|
||||||
|
|
||||||
|
downloaded = ' '.join([
|
||||||
|
req.name for req in requirement_set.successfully_downloaded
|
||||||
|
])
|
||||||
|
if downloaded:
|
||||||
|
logger.info('Successfully downloaded %s', downloaded)
|
||||||
|
|
||||||
|
# Clean up
|
||||||
|
if not options.no_clean:
|
||||||
|
requirement_set.cleanup_files()
|
||||||
|
|
||||||
|
return requirement_set
|
||||||
@ -0,0 +1,96 @@
|
|||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from pip._internal import index
|
||||||
|
from pip._internal.basecommand import Command
|
||||||
|
from pip._internal.cache import WheelCache
|
||||||
|
from pip._internal.compat import stdlib_pkgs
|
||||||
|
from pip._internal.operations.freeze import freeze
|
||||||
|
|
||||||
|
DEV_PKGS = {'pip', 'setuptools', 'distribute', 'wheel'}
|
||||||
|
|
||||||
|
|
||||||
|
class FreezeCommand(Command):
|
||||||
|
"""
|
||||||
|
Output installed packages in requirements format.
|
||||||
|
|
||||||
|
packages are listed in a case-insensitive sorted order.
|
||||||
|
"""
|
||||||
|
name = 'freeze'
|
||||||
|
usage = """
|
||||||
|
%prog [options]"""
|
||||||
|
summary = 'Output installed packages in requirements format.'
|
||||||
|
log_streams = ("ext://sys.stderr", "ext://sys.stderr")
|
||||||
|
|
||||||
|
def __init__(self, *args, **kw):
|
||||||
|
super(FreezeCommand, self).__init__(*args, **kw)
|
||||||
|
|
||||||
|
self.cmd_opts.add_option(
|
||||||
|
'-r', '--requirement',
|
||||||
|
dest='requirements',
|
||||||
|
action='append',
|
||||||
|
default=[],
|
||||||
|
metavar='file',
|
||||||
|
help="Use the order in the given requirements file and its "
|
||||||
|
"comments when generating output. This option can be "
|
||||||
|
"used multiple times.")
|
||||||
|
self.cmd_opts.add_option(
|
||||||
|
'-f', '--find-links',
|
||||||
|
dest='find_links',
|
||||||
|
action='append',
|
||||||
|
default=[],
|
||||||
|
metavar='URL',
|
||||||
|
help='URL for finding packages, which will be added to the '
|
||||||
|
'output.')
|
||||||
|
self.cmd_opts.add_option(
|
||||||
|
'-l', '--local',
|
||||||
|
dest='local',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help='If in a virtualenv that has global access, do not output '
|
||||||
|
'globally-installed packages.')
|
||||||
|
self.cmd_opts.add_option(
|
||||||
|
'--user',
|
||||||
|
dest='user',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help='Only output packages installed in user-site.')
|
||||||
|
self.cmd_opts.add_option(
|
||||||
|
'--all',
|
||||||
|
dest='freeze_all',
|
||||||
|
action='store_true',
|
||||||
|
help='Do not skip these packages in the output:'
|
||||||
|
' %s' % ', '.join(DEV_PKGS))
|
||||||
|
self.cmd_opts.add_option(
|
||||||
|
'--exclude-editable',
|
||||||
|
dest='exclude_editable',
|
||||||
|
action='store_true',
|
||||||
|
help='Exclude editable package from output.')
|
||||||
|
|
||||||
|
self.parser.insert_option_group(0, self.cmd_opts)
|
||||||
|
|
||||||
|
def run(self, options, args):
|
||||||
|
format_control = index.FormatControl(set(), set())
|
||||||
|
wheel_cache = WheelCache(options.cache_dir, format_control)
|
||||||
|
skip = set(stdlib_pkgs)
|
||||||
|
if not options.freeze_all:
|
||||||
|
skip.update(DEV_PKGS)
|
||||||
|
|
||||||
|
freeze_kwargs = dict(
|
||||||
|
requirement=options.requirements,
|
||||||
|
find_links=options.find_links,
|
||||||
|
local_only=options.local,
|
||||||
|
user_only=options.user,
|
||||||
|
skip_regex=options.skip_requirements_regex,
|
||||||
|
isolated=options.isolated_mode,
|
||||||
|
wheel_cache=wheel_cache,
|
||||||
|
skip=skip,
|
||||||
|
exclude_editable=options.exclude_editable,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
for line in freeze(**freeze_kwargs):
|
||||||
|
sys.stdout.write(line + '\n')
|
||||||
|
finally:
|
||||||
|
wheel_cache.cleanup()
|
||||||
@ -0,0 +1,57 @@
|
|||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from pip._internal.basecommand import Command
|
||||||
|
from pip._internal.status_codes import ERROR
|
||||||
|
from pip._internal.utils.hashes import FAVORITE_HASH, STRONG_HASHES
|
||||||
|
from pip._internal.utils.misc import read_chunks
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class HashCommand(Command):
|
||||||
|
"""
|
||||||
|
Compute a hash of a local package archive.
|
||||||
|
|
||||||
|
These can be used with --hash in a requirements file to do repeatable
|
||||||
|
installs.
|
||||||
|
|
||||||
|
"""
|
||||||
|
name = 'hash'
|
||||||
|
usage = '%prog [options] <file> ...'
|
||||||
|
summary = 'Compute hashes of package archives.'
|
||||||
|
ignore_require_venv = True
|
||||||
|
|
||||||
|
def __init__(self, *args, **kw):
|
||||||
|
super(HashCommand, self).__init__(*args, **kw)
|
||||||
|
self.cmd_opts.add_option(
|
||||||
|
'-a', '--algorithm',
|
||||||
|
dest='algorithm',
|
||||||
|
choices=STRONG_HASHES,
|
||||||
|
action='store',
|
||||||
|
default=FAVORITE_HASH,
|
||||||
|
help='The hash algorithm to use: one of %s' %
|
||||||
|
', '.join(STRONG_HASHES))
|
||||||
|
self.parser.insert_option_group(0, self.cmd_opts)
|
||||||
|
|
||||||
|
def run(self, options, args):
|
||||||
|
if not args:
|
||||||
|
self.parser.print_usage(sys.stderr)
|
||||||
|
return ERROR
|
||||||
|
|
||||||
|
algorithm = options.algorithm
|
||||||
|
for path in args:
|
||||||
|
logger.info('%s:\n--hash=%s:%s',
|
||||||
|
path, algorithm, _hash_of_file(path, algorithm))
|
||||||
|
|
||||||
|
|
||||||
|
def _hash_of_file(path, algorithm):
|
||||||
|
"""Return the hash digest of a file."""
|
||||||
|
with open(path, 'rb') as archive:
|
||||||
|
hash = hashlib.new(algorithm)
|
||||||
|
for chunk in read_chunks(archive):
|
||||||
|
hash.update(chunk)
|
||||||
|
return hash.hexdigest()
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
from pip._internal.basecommand import SUCCESS, Command
|
||||||
|
from pip._internal.exceptions import CommandError
|
||||||
|
|
||||||
|
|
||||||
|
class HelpCommand(Command):
|
||||||
|
"""Show help for commands"""
|
||||||
|
name = 'help'
|
||||||
|
usage = """
|
||||||
|
%prog <command>"""
|
||||||
|
summary = 'Show help for commands.'
|
||||||
|
ignore_require_venv = True
|
||||||
|
|
||||||
|
def run(self, options, args):
|
||||||
|
from pip._internal.commands import commands_dict, get_similar_commands
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 'pip help' with no args is handled by pip.__init__.parseopt()
|
||||||
|
cmd_name = args[0] # the command we need help for
|
||||||
|
except IndexError:
|
||||||
|
return SUCCESS
|
||||||
|
|
||||||
|
if cmd_name not in commands_dict:
|
||||||
|
guess = get_similar_commands(cmd_name)
|
||||||
|
|
||||||
|
msg = ['unknown command "%s"' % cmd_name]
|
||||||
|
if guess:
|
||||||
|
msg.append('maybe you meant "%s"' % guess)
|
||||||
|
|
||||||
|
raise CommandError(' - '.join(msg))
|
||||||
|
|
||||||
|
command = commands_dict[cmd_name]()
|
||||||
|
command.parser.print_help()
|
||||||
|
|
||||||
|
return SUCCESS
|
||||||
@ -0,0 +1,502 @@
|
|||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import errno
|
||||||
|
import logging
|
||||||
|
import operator
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
from optparse import SUPPRESS_HELP
|
||||||
|
|
||||||
|
from pip._internal import cmdoptions
|
||||||
|
from pip._internal.basecommand import RequirementCommand
|
||||||
|
from pip._internal.cache import WheelCache
|
||||||
|
from pip._internal.exceptions import (
|
||||||
|
CommandError, InstallationError, PreviousBuildDirError,
|
||||||
|
)
|
||||||
|
from pip._internal.locations import distutils_scheme, virtualenv_no_global
|
||||||
|
from pip._internal.operations.check import check_install_conflicts
|
||||||
|
from pip._internal.operations.prepare import RequirementPreparer
|
||||||
|
from pip._internal.req import RequirementSet, install_given_reqs
|
||||||
|
from pip._internal.resolve import Resolver
|
||||||
|
from pip._internal.status_codes import ERROR
|
||||||
|
from pip._internal.utils.filesystem import check_path_owner
|
||||||
|
from pip._internal.utils.misc import ensure_dir, get_installed_version
|
||||||
|
from pip._internal.utils.temp_dir import TempDirectory
|
||||||
|
from pip._internal.wheel import WheelBuilder
|
||||||
|
|
||||||
|
try:
|
||||||
|
import wheel
|
||||||
|
except ImportError:
|
||||||
|
wheel = None
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class InstallCommand(RequirementCommand):
|
||||||
|
"""
|
||||||
|
Install packages from:
|
||||||
|
|
||||||
|
- PyPI (and other indexes) using requirement specifiers.
|
||||||
|
- VCS project urls.
|
||||||
|
- Local project directories.
|
||||||
|
- Local or remote source archives.
|
||||||
|
|
||||||
|
pip also supports installing from "requirements files", which provide
|
||||||
|
an easy way to specify a whole environment to be installed.
|
||||||
|
"""
|
||||||
|
name = 'install'
|
||||||
|
|
||||||
|
usage = """
|
||||||
|
%prog [options] <requirement specifier> [package-index-options] ...
|
||||||
|
%prog [options] -r <requirements file> [package-index-options] ...
|
||||||
|
%prog [options] [-e] <vcs project url> ...
|
||||||
|
%prog [options] [-e] <local project path> ...
|
||||||
|
%prog [options] <archive url/path> ..."""
|
||||||
|
|
||||||
|
summary = 'Install packages.'
|
||||||
|
|
||||||
|
def __init__(self, *args, **kw):
|
||||||
|
super(InstallCommand, self).__init__(*args, **kw)
|
||||||
|
|
||||||
|
cmd_opts = self.cmd_opts
|
||||||
|
|
||||||
|
cmd_opts.add_option(cmdoptions.requirements())
|
||||||
|
cmd_opts.add_option(cmdoptions.constraints())
|
||||||
|
cmd_opts.add_option(cmdoptions.no_deps())
|
||||||
|
cmd_opts.add_option(cmdoptions.pre())
|
||||||
|
|
||||||
|
cmd_opts.add_option(cmdoptions.editable())
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'-t', '--target',
|
||||||
|
dest='target_dir',
|
||||||
|
metavar='dir',
|
||||||
|
default=None,
|
||||||
|
help='Install packages into <dir>. '
|
||||||
|
'By default this will not replace existing files/folders in '
|
||||||
|
'<dir>. Use --upgrade to replace existing packages in <dir> '
|
||||||
|
'with new versions.'
|
||||||
|
)
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'--user',
|
||||||
|
dest='use_user_site',
|
||||||
|
action='store_true',
|
||||||
|
help="Install to the Python user install directory for your "
|
||||||
|
"platform. Typically ~/.local/, or %APPDATA%\\Python on "
|
||||||
|
"Windows. (See the Python documentation for site.USER_BASE "
|
||||||
|
"for full details.)")
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'--no-user',
|
||||||
|
dest='use_user_site',
|
||||||
|
action='store_false',
|
||||||
|
help=SUPPRESS_HELP)
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'--root',
|
||||||
|
dest='root_path',
|
||||||
|
metavar='dir',
|
||||||
|
default=None,
|
||||||
|
help="Install everything relative to this alternate root "
|
||||||
|
"directory.")
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'--prefix',
|
||||||
|
dest='prefix_path',
|
||||||
|
metavar='dir',
|
||||||
|
default=None,
|
||||||
|
help="Installation prefix where lib, bin and other top-level "
|
||||||
|
"folders are placed")
|
||||||
|
|
||||||
|
cmd_opts.add_option(cmdoptions.build_dir())
|
||||||
|
|
||||||
|
cmd_opts.add_option(cmdoptions.src())
|
||||||
|
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'-U', '--upgrade',
|
||||||
|
dest='upgrade',
|
||||||
|
action='store_true',
|
||||||
|
help='Upgrade all specified packages to the newest available '
|
||||||
|
'version. The handling of dependencies depends on the '
|
||||||
|
'upgrade-strategy used.'
|
||||||
|
)
|
||||||
|
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'--upgrade-strategy',
|
||||||
|
dest='upgrade_strategy',
|
||||||
|
default='only-if-needed',
|
||||||
|
choices=['only-if-needed', 'eager'],
|
||||||
|
help='Determines how dependency upgrading should be handled '
|
||||||
|
'[default: %default]. '
|
||||||
|
'"eager" - dependencies are upgraded regardless of '
|
||||||
|
'whether the currently installed version satisfies the '
|
||||||
|
'requirements of the upgraded package(s). '
|
||||||
|
'"only-if-needed" - are upgraded only when they do not '
|
||||||
|
'satisfy the requirements of the upgraded package(s).'
|
||||||
|
)
|
||||||
|
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'--force-reinstall',
|
||||||
|
dest='force_reinstall',
|
||||||
|
action='store_true',
|
||||||
|
help='Reinstall all packages even if they are already '
|
||||||
|
'up-to-date.')
|
||||||
|
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'-I', '--ignore-installed',
|
||||||
|
dest='ignore_installed',
|
||||||
|
action='store_true',
|
||||||
|
help='Ignore the installed packages (reinstalling instead).')
|
||||||
|
|
||||||
|
cmd_opts.add_option(cmdoptions.ignore_requires_python())
|
||||||
|
cmd_opts.add_option(cmdoptions.no_build_isolation())
|
||||||
|
|
||||||
|
cmd_opts.add_option(cmdoptions.install_options())
|
||||||
|
cmd_opts.add_option(cmdoptions.global_options())
|
||||||
|
|
||||||
|
cmd_opts.add_option(
|
||||||
|
"--compile",
|
||||||
|
action="store_true",
|
||||||
|
dest="compile",
|
||||||
|
default=True,
|
||||||
|
help="Compile Python source files to bytecode",
|
||||||
|
)
|
||||||
|
|
||||||
|
cmd_opts.add_option(
|
||||||
|
"--no-compile",
|
||||||
|
action="store_false",
|
||||||
|
dest="compile",
|
||||||
|
help="Do not compile Python source files to bytecode",
|
||||||
|
)
|
||||||
|
|
||||||
|
cmd_opts.add_option(
|
||||||
|
"--no-warn-script-location",
|
||||||
|
action="store_false",
|
||||||
|
dest="warn_script_location",
|
||||||
|
default=True,
|
||||||
|
help="Do not warn when installing scripts outside PATH",
|
||||||
|
)
|
||||||
|
cmd_opts.add_option(
|
||||||
|
"--no-warn-conflicts",
|
||||||
|
action="store_false",
|
||||||
|
dest="warn_about_conflicts",
|
||||||
|
default=True,
|
||||||
|
help="Do not warn about broken dependencies",
|
||||||
|
)
|
||||||
|
|
||||||
|
cmd_opts.add_option(cmdoptions.no_binary())
|
||||||
|
cmd_opts.add_option(cmdoptions.only_binary())
|
||||||
|
cmd_opts.add_option(cmdoptions.no_clean())
|
||||||
|
cmd_opts.add_option(cmdoptions.require_hashes())
|
||||||
|
cmd_opts.add_option(cmdoptions.progress_bar())
|
||||||
|
|
||||||
|
index_opts = cmdoptions.make_option_group(
|
||||||
|
cmdoptions.index_group,
|
||||||
|
self.parser,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.parser.insert_option_group(0, index_opts)
|
||||||
|
self.parser.insert_option_group(0, cmd_opts)
|
||||||
|
|
||||||
|
def run(self, options, args):
|
||||||
|
cmdoptions.check_install_build_global(options)
|
||||||
|
|
||||||
|
upgrade_strategy = "to-satisfy-only"
|
||||||
|
if options.upgrade:
|
||||||
|
upgrade_strategy = options.upgrade_strategy
|
||||||
|
|
||||||
|
if options.build_dir:
|
||||||
|
options.build_dir = os.path.abspath(options.build_dir)
|
||||||
|
|
||||||
|
options.src_dir = os.path.abspath(options.src_dir)
|
||||||
|
install_options = options.install_options or []
|
||||||
|
if options.use_user_site:
|
||||||
|
if options.prefix_path:
|
||||||
|
raise CommandError(
|
||||||
|
"Can not combine '--user' and '--prefix' as they imply "
|
||||||
|
"different installation locations"
|
||||||
|
)
|
||||||
|
if virtualenv_no_global():
|
||||||
|
raise InstallationError(
|
||||||
|
"Can not perform a '--user' install. User site-packages "
|
||||||
|
"are not visible in this virtualenv."
|
||||||
|
)
|
||||||
|
install_options.append('--user')
|
||||||
|
install_options.append('--prefix=')
|
||||||
|
|
||||||
|
target_temp_dir = TempDirectory(kind="target")
|
||||||
|
if options.target_dir:
|
||||||
|
options.ignore_installed = True
|
||||||
|
options.target_dir = os.path.abspath(options.target_dir)
|
||||||
|
if (os.path.exists(options.target_dir) and not
|
||||||
|
os.path.isdir(options.target_dir)):
|
||||||
|
raise CommandError(
|
||||||
|
"Target path exists but is not a directory, will not "
|
||||||
|
"continue."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create a target directory for using with the target option
|
||||||
|
target_temp_dir.create()
|
||||||
|
install_options.append('--home=' + target_temp_dir.path)
|
||||||
|
|
||||||
|
global_options = options.global_options or []
|
||||||
|
|
||||||
|
with self._build_session(options) as session:
|
||||||
|
finder = self._build_package_finder(options, session)
|
||||||
|
build_delete = (not (options.no_clean or options.build_dir))
|
||||||
|
wheel_cache = WheelCache(options.cache_dir, options.format_control)
|
||||||
|
|
||||||
|
if options.cache_dir and not check_path_owner(options.cache_dir):
|
||||||
|
logger.warning(
|
||||||
|
"The directory '%s' or its parent directory is not owned "
|
||||||
|
"by the current user and caching wheels has been "
|
||||||
|
"disabled. check the permissions and owner of that "
|
||||||
|
"directory. If executing pip with sudo, you may want "
|
||||||
|
"sudo's -H flag.",
|
||||||
|
options.cache_dir,
|
||||||
|
)
|
||||||
|
options.cache_dir = None
|
||||||
|
|
||||||
|
with TempDirectory(
|
||||||
|
options.build_dir, delete=build_delete, kind="install"
|
||||||
|
) as directory:
|
||||||
|
requirement_set = RequirementSet(
|
||||||
|
require_hashes=options.require_hashes,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.populate_requirement_set(
|
||||||
|
requirement_set, args, options, finder, session,
|
||||||
|
self.name, wheel_cache
|
||||||
|
)
|
||||||
|
preparer = RequirementPreparer(
|
||||||
|
build_dir=directory.path,
|
||||||
|
src_dir=options.src_dir,
|
||||||
|
download_dir=None,
|
||||||
|
wheel_download_dir=None,
|
||||||
|
progress_bar=options.progress_bar,
|
||||||
|
build_isolation=options.build_isolation,
|
||||||
|
)
|
||||||
|
|
||||||
|
resolver = Resolver(
|
||||||
|
preparer=preparer,
|
||||||
|
finder=finder,
|
||||||
|
session=session,
|
||||||
|
wheel_cache=wheel_cache,
|
||||||
|
use_user_site=options.use_user_site,
|
||||||
|
upgrade_strategy=upgrade_strategy,
|
||||||
|
force_reinstall=options.force_reinstall,
|
||||||
|
ignore_dependencies=options.ignore_dependencies,
|
||||||
|
ignore_requires_python=options.ignore_requires_python,
|
||||||
|
ignore_installed=options.ignore_installed,
|
||||||
|
isolated=options.isolated_mode,
|
||||||
|
)
|
||||||
|
resolver.resolve(requirement_set)
|
||||||
|
|
||||||
|
# If caching is disabled or wheel is not installed don't
|
||||||
|
# try to build wheels.
|
||||||
|
if wheel and options.cache_dir:
|
||||||
|
# build wheels before install.
|
||||||
|
wb = WheelBuilder(
|
||||||
|
finder, preparer, wheel_cache,
|
||||||
|
build_options=[], global_options=[],
|
||||||
|
)
|
||||||
|
# Ignore the result: a failed wheel will be
|
||||||
|
# installed from the sdist/vcs whatever.
|
||||||
|
wb.build(
|
||||||
|
requirement_set.requirements.values(),
|
||||||
|
session=session, autobuilding=True
|
||||||
|
)
|
||||||
|
|
||||||
|
to_install = resolver.get_installation_order(
|
||||||
|
requirement_set
|
||||||
|
)
|
||||||
|
|
||||||
|
# Consistency Checking of the package set we're installing.
|
||||||
|
should_warn_about_conflicts = (
|
||||||
|
not options.ignore_dependencies and
|
||||||
|
options.warn_about_conflicts
|
||||||
|
)
|
||||||
|
if should_warn_about_conflicts:
|
||||||
|
self._warn_about_conflicts(to_install)
|
||||||
|
|
||||||
|
# Don't warn about script install locations if
|
||||||
|
# --target has been specified
|
||||||
|
warn_script_location = options.warn_script_location
|
||||||
|
if options.target_dir:
|
||||||
|
warn_script_location = False
|
||||||
|
|
||||||
|
installed = install_given_reqs(
|
||||||
|
to_install,
|
||||||
|
install_options,
|
||||||
|
global_options,
|
||||||
|
root=options.root_path,
|
||||||
|
home=target_temp_dir.path,
|
||||||
|
prefix=options.prefix_path,
|
||||||
|
pycompile=options.compile,
|
||||||
|
warn_script_location=warn_script_location,
|
||||||
|
use_user_site=options.use_user_site,
|
||||||
|
)
|
||||||
|
|
||||||
|
possible_lib_locations = get_lib_location_guesses(
|
||||||
|
user=options.use_user_site,
|
||||||
|
home=target_temp_dir.path,
|
||||||
|
root=options.root_path,
|
||||||
|
prefix=options.prefix_path,
|
||||||
|
isolated=options.isolated_mode,
|
||||||
|
)
|
||||||
|
reqs = sorted(installed, key=operator.attrgetter('name'))
|
||||||
|
items = []
|
||||||
|
for req in reqs:
|
||||||
|
item = req.name
|
||||||
|
try:
|
||||||
|
installed_version = get_installed_version(
|
||||||
|
req.name, possible_lib_locations
|
||||||
|
)
|
||||||
|
if installed_version:
|
||||||
|
item += '-' + installed_version
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
items.append(item)
|
||||||
|
installed = ' '.join(items)
|
||||||
|
if installed:
|
||||||
|
logger.info('Successfully installed %s', installed)
|
||||||
|
except EnvironmentError as error:
|
||||||
|
show_traceback = (self.verbosity >= 1)
|
||||||
|
|
||||||
|
message = create_env_error_message(
|
||||||
|
error, show_traceback, options.use_user_site,
|
||||||
|
)
|
||||||
|
logger.error(message, exc_info=show_traceback)
|
||||||
|
|
||||||
|
return ERROR
|
||||||
|
except PreviousBuildDirError:
|
||||||
|
options.no_clean = True
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
# Clean up
|
||||||
|
if not options.no_clean:
|
||||||
|
requirement_set.cleanup_files()
|
||||||
|
wheel_cache.cleanup()
|
||||||
|
|
||||||
|
if options.target_dir:
|
||||||
|
self._handle_target_dir(
|
||||||
|
options.target_dir, target_temp_dir, options.upgrade
|
||||||
|
)
|
||||||
|
return requirement_set
|
||||||
|
|
||||||
|
def _handle_target_dir(self, target_dir, target_temp_dir, upgrade):
|
||||||
|
ensure_dir(target_dir)
|
||||||
|
|
||||||
|
# Checking both purelib and platlib directories for installed
|
||||||
|
# packages to be moved to target directory
|
||||||
|
lib_dir_list = []
|
||||||
|
|
||||||
|
with target_temp_dir:
|
||||||
|
# Checking both purelib and platlib directories for installed
|
||||||
|
# packages to be moved to target directory
|
||||||
|
scheme = distutils_scheme('', home=target_temp_dir.path)
|
||||||
|
purelib_dir = scheme['purelib']
|
||||||
|
platlib_dir = scheme['platlib']
|
||||||
|
data_dir = scheme['data']
|
||||||
|
|
||||||
|
if os.path.exists(purelib_dir):
|
||||||
|
lib_dir_list.append(purelib_dir)
|
||||||
|
if os.path.exists(platlib_dir) and platlib_dir != purelib_dir:
|
||||||
|
lib_dir_list.append(platlib_dir)
|
||||||
|
if os.path.exists(data_dir):
|
||||||
|
lib_dir_list.append(data_dir)
|
||||||
|
|
||||||
|
for lib_dir in lib_dir_list:
|
||||||
|
for item in os.listdir(lib_dir):
|
||||||
|
if lib_dir == data_dir:
|
||||||
|
ddir = os.path.join(data_dir, item)
|
||||||
|
if any(s.startswith(ddir) for s in lib_dir_list[:-1]):
|
||||||
|
continue
|
||||||
|
target_item_dir = os.path.join(target_dir, item)
|
||||||
|
if os.path.exists(target_item_dir):
|
||||||
|
if not upgrade:
|
||||||
|
logger.warning(
|
||||||
|
'Target directory %s already exists. Specify '
|
||||||
|
'--upgrade to force replacement.',
|
||||||
|
target_item_dir
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
if os.path.islink(target_item_dir):
|
||||||
|
logger.warning(
|
||||||
|
'Target directory %s already exists and is '
|
||||||
|
'a link. Pip will not automatically replace '
|
||||||
|
'links, please remove if replacement is '
|
||||||
|
'desired.',
|
||||||
|
target_item_dir
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
if os.path.isdir(target_item_dir):
|
||||||
|
shutil.rmtree(target_item_dir)
|
||||||
|
else:
|
||||||
|
os.remove(target_item_dir)
|
||||||
|
|
||||||
|
shutil.move(
|
||||||
|
os.path.join(lib_dir, item),
|
||||||
|
target_item_dir
|
||||||
|
)
|
||||||
|
|
||||||
|
def _warn_about_conflicts(self, to_install):
|
||||||
|
package_set, _dep_info = check_install_conflicts(to_install)
|
||||||
|
missing, conflicting = _dep_info
|
||||||
|
|
||||||
|
# NOTE: There is some duplication here from pip check
|
||||||
|
for project_name in missing:
|
||||||
|
version = package_set[project_name][0]
|
||||||
|
for dependency in missing[project_name]:
|
||||||
|
logger.critical(
|
||||||
|
"%s %s requires %s, which is not installed.",
|
||||||
|
project_name, version, dependency[1],
|
||||||
|
)
|
||||||
|
|
||||||
|
for project_name in conflicting:
|
||||||
|
version = package_set[project_name][0]
|
||||||
|
for dep_name, dep_version, req in conflicting[project_name]:
|
||||||
|
logger.critical(
|
||||||
|
"%s %s has requirement %s, but you'll have %s %s which is "
|
||||||
|
"incompatible.",
|
||||||
|
project_name, version, req, dep_name, dep_version,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_lib_location_guesses(*args, **kwargs):
|
||||||
|
scheme = distutils_scheme('', *args, **kwargs)
|
||||||
|
return [scheme['purelib'], scheme['platlib']]
|
||||||
|
|
||||||
|
|
||||||
|
def create_env_error_message(error, show_traceback, using_user_site):
|
||||||
|
"""Format an error message for an EnvironmentError
|
||||||
|
|
||||||
|
It may occur anytime during the execution of the install command.
|
||||||
|
"""
|
||||||
|
parts = []
|
||||||
|
|
||||||
|
# Mention the error if we are not going to show a traceback
|
||||||
|
parts.append("Could not install packages due to an EnvironmentError")
|
||||||
|
if not show_traceback:
|
||||||
|
parts.append(": ")
|
||||||
|
parts.append(str(error))
|
||||||
|
else:
|
||||||
|
parts.append(".")
|
||||||
|
|
||||||
|
# Spilt the error indication from a helper message (if any)
|
||||||
|
parts[-1] += "\n"
|
||||||
|
|
||||||
|
# Suggest useful actions to the user:
|
||||||
|
# (1) using user site-packages or (2) verifying the permissions
|
||||||
|
if error.errno == errno.EACCES:
|
||||||
|
user_option_part = "Consider using the `--user` option"
|
||||||
|
permissions_part = "Check the permissions"
|
||||||
|
|
||||||
|
if not using_user_site:
|
||||||
|
parts.extend([
|
||||||
|
user_option_part, " or ",
|
||||||
|
permissions_part.lower(),
|
||||||
|
])
|
||||||
|
else:
|
||||||
|
parts.append(permissions_part)
|
||||||
|
parts.append(".\n")
|
||||||
|
|
||||||
|
return "".join(parts).strip() + "\n"
|
||||||
@ -0,0 +1,343 @@
|
|||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
from pip._vendor import six
|
||||||
|
from pip._vendor.six.moves import zip_longest
|
||||||
|
|
||||||
|
from pip._internal.basecommand import Command
|
||||||
|
from pip._internal.cmdoptions import index_group, make_option_group
|
||||||
|
from pip._internal.exceptions import CommandError
|
||||||
|
from pip._internal.index import PackageFinder
|
||||||
|
from pip._internal.utils.deprecation import RemovedInPip11Warning
|
||||||
|
from pip._internal.utils.misc import (
|
||||||
|
dist_is_editable, get_installed_distributions,
|
||||||
|
)
|
||||||
|
from pip._internal.utils.packaging import get_installer
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ListCommand(Command):
|
||||||
|
"""
|
||||||
|
List installed packages, including editables.
|
||||||
|
|
||||||
|
Packages are listed in a case-insensitive sorted order.
|
||||||
|
"""
|
||||||
|
name = 'list'
|
||||||
|
usage = """
|
||||||
|
%prog [options]"""
|
||||||
|
summary = 'List installed packages.'
|
||||||
|
|
||||||
|
def __init__(self, *args, **kw):
|
||||||
|
super(ListCommand, self).__init__(*args, **kw)
|
||||||
|
|
||||||
|
cmd_opts = self.cmd_opts
|
||||||
|
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'-o', '--outdated',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help='List outdated packages')
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'-u', '--uptodate',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help='List uptodate packages')
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'-e', '--editable',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help='List editable projects.')
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'-l', '--local',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help=('If in a virtualenv that has global access, do not list '
|
||||||
|
'globally-installed packages.'),
|
||||||
|
)
|
||||||
|
self.cmd_opts.add_option(
|
||||||
|
'--user',
|
||||||
|
dest='user',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help='Only output packages installed in user-site.')
|
||||||
|
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'--pre',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
help=("Include pre-release and development versions. By default, "
|
||||||
|
"pip only finds stable versions."),
|
||||||
|
)
|
||||||
|
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'--format',
|
||||||
|
action='store',
|
||||||
|
dest='list_format',
|
||||||
|
default="columns",
|
||||||
|
choices=('legacy', 'columns', 'freeze', 'json'),
|
||||||
|
help="Select the output format among: columns (default), freeze, "
|
||||||
|
"json, or legacy.",
|
||||||
|
)
|
||||||
|
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'--not-required',
|
||||||
|
action='store_true',
|
||||||
|
dest='not_required',
|
||||||
|
help="List packages that are not dependencies of "
|
||||||
|
"installed packages.",
|
||||||
|
)
|
||||||
|
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'--exclude-editable',
|
||||||
|
action='store_false',
|
||||||
|
dest='include_editable',
|
||||||
|
help='Exclude editable package from output.',
|
||||||
|
)
|
||||||
|
cmd_opts.add_option(
|
||||||
|
'--include-editable',
|
||||||
|
action='store_true',
|
||||||
|
dest='include_editable',
|
||||||
|
help='Include editable package from output.',
|
||||||
|
default=True,
|
||||||
|
)
|
||||||
|
index_opts = make_option_group(index_group, self.parser)
|
||||||
|
|
||||||
|
self.parser.insert_option_group(0, index_opts)
|
||||||
|
self.parser.insert_option_group(0, cmd_opts)
|
||||||
|
|
||||||
|
def _build_package_finder(self, options, index_urls, session):
|
||||||
|
"""
|
||||||
|
Create a package finder appropriate to this list command.
|
||||||
|
"""
|
||||||
|
return PackageFinder(
|
||||||
|
find_links=options.find_links,
|
||||||
|
index_urls=index_urls,
|
||||||
|
allow_all_prereleases=options.pre,
|
||||||
|
trusted_hosts=options.trusted_hosts,
|
||||||
|
process_dependency_links=options.process_dependency_links,
|
||||||
|
session=session,
|
||||||
|
)
|
||||||
|
|
||||||
|
def run(self, options, args):
|
||||||
|
if options.list_format == "legacy":
|
||||||
|
warnings.warn(
|
||||||
|
"The legacy format has been deprecated and will be removed "
|
||||||
|
"in the future.",
|
||||||
|
RemovedInPip11Warning,
|
||||||
|
)
|
||||||
|
|
||||||
|
if options.outdated and options.uptodate:
|
||||||
|
raise CommandError(
|
||||||
|
"Options --outdated and --uptodate cannot be combined.")
|
||||||
|
|
||||||
|
packages = get_installed_distributions(
|
||||||
|
local_only=options.local,
|
||||||
|
user_only=options.user,
|
||||||
|
editables_only=options.editable,
|
||||||
|
include_editables=options.include_editable,
|
||||||
|
)
|
||||||
|
|
||||||
|
if options.outdated:
|
||||||
|
packages = self.get_outdated(packages, options)
|
||||||
|
elif options.uptodate:
|
||||||
|
packages = self.get_uptodate(packages, options)
|
||||||
|
|
||||||
|
if options.not_required:
|
||||||
|
packages = self.get_not_required(packages, options)
|
||||||
|
|
||||||
|
self.output_package_listing(packages, options)
|
||||||
|
|
||||||
|
def get_outdated(self, packages, options):
|
||||||
|
return [
|
||||||
|
dist for dist in self.iter_packages_latest_infos(packages, options)
|
||||||
|
if dist.latest_version > dist.parsed_version
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_uptodate(self, packages, options):
|
||||||
|
return [
|
||||||
|
dist for dist in self.iter_packages_latest_infos(packages, options)
|
||||||
|
if dist.latest_version == dist.parsed_version
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_not_required(self, packages, options):
|
||||||
|
dep_keys = set()
|
||||||
|
for dist in packages:
|
||||||
|
dep_keys.update(requirement.key for requirement in dist.requires())
|
||||||
|
return {pkg for pkg in packages if pkg.key not in dep_keys}
|
||||||
|
|
||||||
|
def iter_packages_latest_infos(self, packages, options):
|
||||||
|
index_urls = [options.index_url] + options.extra_index_urls
|
||||||
|
if options.no_index:
|
||||||
|
logger.debug('Ignoring indexes: %s', ','.join(index_urls))
|
||||||
|
index_urls = []
|
||||||
|
|
||||||
|
dependency_links = []
|
||||||
|
for dist in packages:
|
||||||
|
if dist.has_metadata('dependency_links.txt'):
|
||||||
|
dependency_links.extend(
|
||||||
|
dist.get_metadata_lines('dependency_links.txt'),
|
||||||
|
)
|
||||||
|
|
||||||
|
with self._build_session(options) as session:
|
||||||
|
finder = self._build_package_finder(options, index_urls, session)
|
||||||
|
finder.add_dependency_links(dependency_links)
|
||||||
|
|
||||||
|
for dist in packages:
|
||||||
|
typ = 'unknown'
|
||||||
|
all_candidates = finder.find_all_candidates(dist.key)
|
||||||
|
if not options.pre:
|
||||||
|
# Remove prereleases
|
||||||
|
all_candidates = [candidate for candidate in all_candidates
|
||||||
|
if not candidate.version.is_prerelease]
|
||||||
|
|
||||||
|
if not all_candidates:
|
||||||
|
continue
|
||||||
|
best_candidate = max(all_candidates,
|
||||||
|
key=finder._candidate_sort_key)
|
||||||
|
remote_version = best_candidate.version
|
||||||
|
if best_candidate.location.is_wheel:
|
||||||
|
typ = 'wheel'
|
||||||
|
else:
|
||||||
|
typ = 'sdist'
|
||||||
|
# This is dirty but makes the rest of the code much cleaner
|
||||||
|
dist.latest_version = remote_version
|
||||||
|
dist.latest_filetype = typ
|
||||||
|
yield dist
|
||||||
|
|
||||||
|
def output_legacy(self, dist, options):
|
||||||
|
if options.verbose >= 1:
|
||||||
|
return '%s (%s, %s, %s)' % (
|
||||||
|
dist.project_name,
|
||||||
|
dist.version,
|
||||||
|
dist.location,
|
||||||
|
get_installer(dist),
|
||||||
|
)
|
||||||
|
elif dist_is_editable(dist):
|
||||||
|
return '%s (%s, %s)' % (
|
||||||
|
dist.project_name,
|
||||||
|
dist.version,
|
||||||
|
dist.location,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return '%s (%s)' % (dist.project_name, dist.version)
|
||||||
|
|
||||||
|
def output_legacy_latest(self, dist, options):
|
||||||
|
return '%s - Latest: %s [%s]' % (
|
||||||
|
self.output_legacy(dist, options),
|
||||||
|
dist.latest_version,
|
||||||
|
dist.latest_filetype,
|
||||||
|
)
|
||||||
|
|
||||||
|
def output_package_listing(self, packages, options):
|
||||||
|
packages = sorted(
|
||||||
|
packages,
|
||||||
|
key=lambda dist: dist.project_name.lower(),
|
||||||
|
)
|
||||||
|
if options.list_format == 'columns' and packages:
|
||||||
|
data, header = format_for_columns(packages, options)
|
||||||
|
self.output_package_listing_columns(data, header)
|
||||||
|
elif options.list_format == 'freeze':
|
||||||
|
for dist in packages:
|
||||||
|
if options.verbose >= 1:
|
||||||
|
logger.info("%s==%s (%s)", dist.project_name,
|
||||||
|
dist.version, dist.location)
|
||||||
|
else:
|
||||||
|
logger.info("%s==%s", dist.project_name, dist.version)
|
||||||
|
elif options.list_format == 'json':
|
||||||
|
logger.info(format_for_json(packages, options))
|
||||||
|
elif options.list_format == "legacy":
|
||||||
|
for dist in packages:
|
||||||
|
if options.outdated:
|
||||||
|
logger.info(self.output_legacy_latest(dist, options))
|
||||||
|
else:
|
||||||
|
logger.info(self.output_legacy(dist, options))
|
||||||
|
|
||||||
|
def output_package_listing_columns(self, data, header):
|
||||||
|
# insert the header first: we need to know the size of column names
|
||||||
|
if len(data) > 0:
|
||||||
|
data.insert(0, header)
|
||||||
|
|
||||||
|
pkg_strings, sizes = tabulate(data)
|
||||||
|
|
||||||
|
# Create and add a separator.
|
||||||
|
if len(data) > 0:
|
||||||
|
pkg_strings.insert(1, " ".join(map(lambda x: '-' * x, sizes)))
|
||||||
|
|
||||||
|
for val in pkg_strings:
|
||||||
|
logger.info(val)
|
||||||
|
|
||||||
|
|
||||||
|
def tabulate(vals):
|
||||||
|
# From pfmoore on GitHub:
|
||||||
|
# https://github.com/pypa/pip/issues/3651#issuecomment-216932564
|
||||||
|
assert len(vals) > 0
|
||||||
|
|
||||||
|
sizes = [0] * max(len(x) for x in vals)
|
||||||
|
for row in vals:
|
||||||
|
sizes = [max(s, len(str(c))) for s, c in zip_longest(sizes, row)]
|
||||||
|
|
||||||
|
result = []
|
||||||
|
for row in vals:
|
||||||
|
display = " ".join([str(c).ljust(s) if c is not None else ''
|
||||||
|
for s, c in zip_longest(sizes, row)])
|
||||||
|
result.append(display)
|
||||||
|
|
||||||
|
return result, sizes
|
||||||
|
|
||||||
|
|
||||||
|
def format_for_columns(pkgs, options):
|
||||||
|
"""
|
||||||
|
Convert the package data into something usable
|
||||||
|
by output_package_listing_columns.
|
||||||
|
"""
|
||||||
|
running_outdated = options.outdated
|
||||||
|
# Adjust the header for the `pip list --outdated` case.
|
||||||
|
if running_outdated:
|
||||||
|
header = ["Package", "Version", "Latest", "Type"]
|
||||||
|
else:
|
||||||
|
header = ["Package", "Version"]
|
||||||
|
|
||||||
|
data = []
|
||||||
|
if options.verbose >= 1 or any(dist_is_editable(x) for x in pkgs):
|
||||||
|
header.append("Location")
|
||||||
|
if options.verbose >= 1:
|
||||||
|
header.append("Installer")
|
||||||
|
|
||||||
|
for proj in pkgs:
|
||||||
|
# if we're working on the 'outdated' list, separate out the
|
||||||
|
# latest_version and type
|
||||||
|
row = [proj.project_name, proj.version]
|
||||||
|
|
||||||
|
if running_outdated:
|
||||||
|
row.append(proj.latest_version)
|
||||||
|
row.append(proj.latest_filetype)
|
||||||
|
|
||||||
|
if options.verbose >= 1 or dist_is_editable(proj):
|
||||||
|
row.append(proj.location)
|
||||||
|
if options.verbose >= 1:
|
||||||
|
row.append(get_installer(proj))
|
||||||
|
|
||||||
|
data.append(row)
|
||||||
|
|
||||||
|
return data, header
|
||||||
|
|
||||||
|
|
||||||
|
def format_for_json(packages, options):
|
||||||
|
data = []
|
||||||
|
for dist in packages:
|
||||||
|
info = {
|
||||||
|
'name': dist.project_name,
|
||||||
|
'version': six.text_type(dist.version),
|
||||||
|
}
|
||||||
|
if options.verbose >= 1:
|
||||||
|
info['location'] = dist.location
|
||||||
|
info['installer'] = get_installer(dist)
|
||||||
|
if options.outdated:
|
||||||
|
info['latest_version'] = six.text_type(dist.latest_version)
|
||||||
|
info['latest_filetype'] = dist.latest_filetype
|
||||||
|
data.append(info)
|
||||||
|
return json.dumps(data)
|
||||||
@ -0,0 +1,135 @@
|
|||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
import textwrap
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
from pip._vendor import pkg_resources
|
||||||
|
from pip._vendor.packaging.version import parse as parse_version
|
||||||
|
# NOTE: XMLRPC Client is not annotated in typeshed as on 2017-07-17, which is
|
||||||
|
# why we ignore the type on this import
|
||||||
|
from pip._vendor.six.moves import xmlrpc_client # type: ignore
|
||||||
|
|
||||||
|
from pip._internal.basecommand import SUCCESS, Command
|
||||||
|
from pip._internal.compat import get_terminal_size
|
||||||
|
from pip._internal.download import PipXmlrpcTransport
|
||||||
|
from pip._internal.exceptions import CommandError
|
||||||
|
from pip._internal.models import PyPI
|
||||||
|
from pip._internal.status_codes import NO_MATCHES_FOUND
|
||||||
|
from pip._internal.utils.logging import indent_log
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class SearchCommand(Command):
|
||||||
|
"""Search for PyPI packages whose name or summary contains <query>."""
|
||||||
|
name = 'search'
|
||||||
|
usage = """
|
||||||
|
%prog [options] <query>"""
|
||||||
|
summary = 'Search PyPI for packages.'
|
||||||
|
ignore_require_venv = True
|
||||||
|
|
||||||
|
def __init__(self, *args, **kw):
|
||||||
|
super(SearchCommand, self).__init__(*args, **kw)
|
||||||
|
self.cmd_opts.add_option(
|
||||||
|
'-i', '--index',
|
||||||
|
dest='index',
|
||||||
|
metavar='URL',
|
||||||
|
default=PyPI.pypi_url,
|
||||||
|
help='Base URL of Python Package Index (default %default)')
|
||||||
|
|
||||||
|
self.parser.insert_option_group(0, self.cmd_opts)
|
||||||
|
|
||||||
|
def run(self, options, args):
|
||||||
|
if not args:
|
||||||
|
raise CommandError('Missing required argument (search query).')
|
||||||
|
query = args
|
||||||
|
pypi_hits = self.search(query, options)
|
||||||
|
hits = transform_hits(pypi_hits)
|
||||||
|
|
||||||
|
terminal_width = None
|
||||||
|
if sys.stdout.isatty():
|
||||||
|
terminal_width = get_terminal_size()[0]
|
||||||
|
|
||||||
|
print_results(hits, terminal_width=terminal_width)
|
||||||
|
if pypi_hits:
|
||||||
|
return SUCCESS
|
||||||
|
return NO_MATCHES_FOUND
|
||||||
|
|
||||||
|
def search(self, query, options):
|
||||||
|
index_url = options.index
|
||||||
|
with self._build_session(options) as session:
|
||||||
|
transport = PipXmlrpcTransport(index_url, session)
|
||||||
|
pypi = xmlrpc_client.ServerProxy(index_url, transport)
|
||||||
|
hits = pypi.search({'name': query, 'summary': query}, 'or')
|
||||||
|
return hits
|
||||||
|
|
||||||
|
|
||||||
|
def transform_hits(hits):
|
||||||
|
"""
|
||||||
|
The list from pypi is really a list of versions. We want a list of
|
||||||
|
packages with the list of versions stored inline. This converts the
|
||||||
|
list from pypi into one we can use.
|
||||||
|
"""
|
||||||
|
packages = OrderedDict()
|
||||||
|
for hit in hits:
|
||||||
|
name = hit['name']
|
||||||
|
summary = hit['summary']
|
||||||
|
version = hit['version']
|
||||||
|
|
||||||
|
if name not in packages.keys():
|
||||||
|
packages[name] = {
|
||||||
|
'name': name,
|
||||||
|
'summary': summary,
|
||||||
|
'versions': [version],
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
packages[name]['versions'].append(version)
|
||||||
|
|
||||||
|
# if this is the highest version, replace summary and score
|
||||||
|
if version == highest_version(packages[name]['versions']):
|
||||||
|
packages[name]['summary'] = summary
|
||||||
|
|
||||||
|
return list(packages.values())
|
||||||
|
|
||||||
|
|
||||||
|
def print_results(hits, name_column_width=None, terminal_width=None):
|
||||||
|
if not hits:
|
||||||
|
return
|
||||||
|
if name_column_width is None:
|
||||||
|
name_column_width = max([
|
||||||
|
len(hit['name']) + len(highest_version(hit.get('versions', ['-'])))
|
||||||
|
for hit in hits
|
||||||
|
]) + 4
|
||||||
|
|
||||||
|
installed_packages = [p.project_name for p in pkg_resources.working_set]
|
||||||
|
for hit in hits:
|
||||||
|
name = hit['name']
|
||||||
|
summary = hit['summary'] or ''
|
||||||
|
latest = highest_version(hit.get('versions', ['-']))
|
||||||
|
if terminal_width is not None:
|
||||||
|
target_width = terminal_width - name_column_width - 5
|
||||||
|
if target_width > 10:
|
||||||
|
# wrap and indent summary to fit terminal
|
||||||
|
summary = textwrap.wrap(summary, target_width)
|
||||||
|
summary = ('\n' + ' ' * (name_column_width + 3)).join(summary)
|
||||||
|
|
||||||
|
line = '%-*s - %s' % (name_column_width,
|
||||||
|
'%s (%s)' % (name, latest), summary)
|
||||||
|
try:
|
||||||
|
logger.info(line)
|
||||||
|
if name in installed_packages:
|
||||||
|
dist = pkg_resources.get_distribution(name)
|
||||||
|
with indent_log():
|
||||||
|
if dist.version == latest:
|
||||||
|
logger.info('INSTALLED: %s (latest)', dist.version)
|
||||||
|
else:
|
||||||
|
logger.info('INSTALLED: %s', dist.version)
|
||||||
|
logger.info('LATEST: %s', latest)
|
||||||
|
except UnicodeEncodeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def highest_version(versions):
|
||||||
|
return max(versions, key=parse_version)
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user