mirror of
https://github.com/YunoHost-Apps/zwave-js-ui_ynh.git
synced 2024-09-03 18:06:00 +02:00
Upgrade to v8.14.1
This commit is contained in:
parent
1c4b57db58
commit
9c693c0197
1469 changed files with 3 additions and 259437 deletions
|
@ -1,5 +1,5 @@
|
||||||
SOURCE_URL=https://api.github.com/repos/zwave-js/zwave-js-ui/zipball/v8.13.1
|
SOURCE_URL=https://api.github.com/repos/zwave-js/zwave-js-ui/zipball/v8.14.1
|
||||||
SOURCE_SUM=0322fe49f93ffc4a725d77d3873469b9ae583e025ea6ef3cd8e7640e203b7938
|
SOURCE_SUM=9a6a4c66dc915e207e3bcb7957b55409d2487081d4ffd5bf8de3e9ec2df03503
|
||||||
SOURCE_SUM_PRG=sha256sum
|
SOURCE_SUM_PRG=sha256sum
|
||||||
SOURCE_FORMAT=zip
|
SOURCE_FORMAT=zip
|
||||||
SOURCE_IN_SUBDIR=true
|
SOURCE_IN_SUBDIR=true
|
||||||
|
|
|
@ -5,7 +5,7 @@ name = "Zwave-JS-UI"
|
||||||
description.en = "Full featured Z-Wave Control Panel and MQTT Gateway integrated with domoticz"
|
description.en = "Full featured Z-Wave Control Panel and MQTT Gateway integrated with domoticz"
|
||||||
description.fr = "Panneau de controle Z-Wave et MQTT intégré avec Domoticz"
|
description.fr = "Panneau de controle Z-Wave et MQTT intégré avec Domoticz"
|
||||||
|
|
||||||
version = "8.13.1~ynh1"
|
version = "8.14.1~ynh1"
|
||||||
|
|
||||||
maintainers = ["Krakinou"]
|
maintainers = ["Krakinou"]
|
||||||
|
|
||||||
|
|
|
@ -1,247 +0,0 @@
|
||||||
<#
|
|
||||||
.Synopsis
|
|
||||||
Activate a Python virtual environment for the current PowerShell session.
|
|
||||||
|
|
||||||
.Description
|
|
||||||
Pushes the python executable for a virtual environment to the front of the
|
|
||||||
$Env:PATH environment variable and sets the prompt to signify that you are
|
|
||||||
in a Python virtual environment. Makes use of the command line switches as
|
|
||||||
well as the `pyvenv.cfg` file values present in the virtual environment.
|
|
||||||
|
|
||||||
.Parameter VenvDir
|
|
||||||
Path to the directory that contains the virtual environment to activate. The
|
|
||||||
default value for this is the parent of the directory that the Activate.ps1
|
|
||||||
script is located within.
|
|
||||||
|
|
||||||
.Parameter Prompt
|
|
||||||
The prompt prefix to display when this virtual environment is activated. By
|
|
||||||
default, this prompt is the name of the virtual environment folder (VenvDir)
|
|
||||||
surrounded by parentheses and followed by a single space (ie. '(.venv) ').
|
|
||||||
|
|
||||||
.Example
|
|
||||||
Activate.ps1
|
|
||||||
Activates the Python virtual environment that contains the Activate.ps1 script.
|
|
||||||
|
|
||||||
.Example
|
|
||||||
Activate.ps1 -Verbose
|
|
||||||
Activates the Python virtual environment that contains the Activate.ps1 script,
|
|
||||||
and shows extra information about the activation as it executes.
|
|
||||||
|
|
||||||
.Example
|
|
||||||
Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv
|
|
||||||
Activates the Python virtual environment located in the specified location.
|
|
||||||
|
|
||||||
.Example
|
|
||||||
Activate.ps1 -Prompt "MyPython"
|
|
||||||
Activates the Python virtual environment that contains the Activate.ps1 script,
|
|
||||||
and prefixes the current prompt with the specified string (surrounded in
|
|
||||||
parentheses) while the virtual environment is active.
|
|
||||||
|
|
||||||
.Notes
|
|
||||||
On Windows, it may be required to enable this Activate.ps1 script by setting the
|
|
||||||
execution policy for the user. You can do this by issuing the following PowerShell
|
|
||||||
command:
|
|
||||||
|
|
||||||
PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
|
|
||||||
|
|
||||||
For more information on Execution Policies:
|
|
||||||
https://go.microsoft.com/fwlink/?LinkID=135170
|
|
||||||
|
|
||||||
#>
|
|
||||||
Param(
|
|
||||||
[Parameter(Mandatory = $false)]
|
|
||||||
[String]
|
|
||||||
$VenvDir,
|
|
||||||
[Parameter(Mandatory = $false)]
|
|
||||||
[String]
|
|
||||||
$Prompt
|
|
||||||
)
|
|
||||||
|
|
||||||
<# Function declarations --------------------------------------------------- #>
|
|
||||||
|
|
||||||
<#
|
|
||||||
.Synopsis
|
|
||||||
Remove all shell session elements added by the Activate script, including the
|
|
||||||
addition of the virtual environment's Python executable from the beginning of
|
|
||||||
the PATH variable.
|
|
||||||
|
|
||||||
.Parameter NonDestructive
|
|
||||||
If present, do not remove this function from the global namespace for the
|
|
||||||
session.
|
|
||||||
|
|
||||||
#>
|
|
||||||
function global:deactivate ([switch]$NonDestructive) {
|
|
||||||
# Revert to original values
|
|
||||||
|
|
||||||
# The prior prompt:
|
|
||||||
if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) {
|
|
||||||
Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt
|
|
||||||
Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT
|
|
||||||
}
|
|
||||||
|
|
||||||
# The prior PYTHONHOME:
|
|
||||||
if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) {
|
|
||||||
Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME
|
|
||||||
Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME
|
|
||||||
}
|
|
||||||
|
|
||||||
# The prior PATH:
|
|
||||||
if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) {
|
|
||||||
Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH
|
|
||||||
Remove-Item -Path Env:_OLD_VIRTUAL_PATH
|
|
||||||
}
|
|
||||||
|
|
||||||
# Just remove the VIRTUAL_ENV altogether:
|
|
||||||
if (Test-Path -Path Env:VIRTUAL_ENV) {
|
|
||||||
Remove-Item -Path env:VIRTUAL_ENV
|
|
||||||
}
|
|
||||||
|
|
||||||
# Just remove VIRTUAL_ENV_PROMPT altogether.
|
|
||||||
if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) {
|
|
||||||
Remove-Item -Path env:VIRTUAL_ENV_PROMPT
|
|
||||||
}
|
|
||||||
|
|
||||||
# Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether:
|
|
||||||
if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) {
|
|
||||||
Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force
|
|
||||||
}
|
|
||||||
|
|
||||||
# Leave deactivate function in the global namespace if requested:
|
|
||||||
if (-not $NonDestructive) {
|
|
||||||
Remove-Item -Path function:deactivate
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
<#
|
|
||||||
.Description
|
|
||||||
Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the
|
|
||||||
given folder, and returns them in a map.
|
|
||||||
|
|
||||||
For each line in the pyvenv.cfg file, if that line can be parsed into exactly
|
|
||||||
two strings separated by `=` (with any amount of whitespace surrounding the =)
|
|
||||||
then it is considered a `key = value` line. The left hand string is the key,
|
|
||||||
the right hand is the value.
|
|
||||||
|
|
||||||
If the value starts with a `'` or a `"` then the first and last character is
|
|
||||||
stripped from the value before being captured.
|
|
||||||
|
|
||||||
.Parameter ConfigDir
|
|
||||||
Path to the directory that contains the `pyvenv.cfg` file.
|
|
||||||
#>
|
|
||||||
function Get-PyVenvConfig(
|
|
||||||
[String]
|
|
||||||
$ConfigDir
|
|
||||||
) {
|
|
||||||
Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg"
|
|
||||||
|
|
||||||
# Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue).
|
|
||||||
$pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue
|
|
||||||
|
|
||||||
# An empty map will be returned if no config file is found.
|
|
||||||
$pyvenvConfig = @{ }
|
|
||||||
|
|
||||||
if ($pyvenvConfigPath) {
|
|
||||||
|
|
||||||
Write-Verbose "File exists, parse `key = value` lines"
|
|
||||||
$pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath
|
|
||||||
|
|
||||||
$pyvenvConfigContent | ForEach-Object {
|
|
||||||
$keyval = $PSItem -split "\s*=\s*", 2
|
|
||||||
if ($keyval[0] -and $keyval[1]) {
|
|
||||||
$val = $keyval[1]
|
|
||||||
|
|
||||||
# Remove extraneous quotations around a string value.
|
|
||||||
if ("'""".Contains($val.Substring(0, 1))) {
|
|
||||||
$val = $val.Substring(1, $val.Length - 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
$pyvenvConfig[$keyval[0]] = $val
|
|
||||||
Write-Verbose "Adding Key: '$($keyval[0])'='$val'"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $pyvenvConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
<# Begin Activate script --------------------------------------------------- #>
|
|
||||||
|
|
||||||
# Determine the containing directory of this script
|
|
||||||
$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
|
||||||
$VenvExecDir = Get-Item -Path $VenvExecPath
|
|
||||||
|
|
||||||
Write-Verbose "Activation script is located in path: '$VenvExecPath'"
|
|
||||||
Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)"
|
|
||||||
Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)"
|
|
||||||
|
|
||||||
# Set values required in priority: CmdLine, ConfigFile, Default
|
|
||||||
# First, get the location of the virtual environment, it might not be
|
|
||||||
# VenvExecDir if specified on the command line.
|
|
||||||
if ($VenvDir) {
|
|
||||||
Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values"
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir."
|
|
||||||
$VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/")
|
|
||||||
Write-Verbose "VenvDir=$VenvDir"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Next, read the `pyvenv.cfg` file to determine any required value such
|
|
||||||
# as `prompt`.
|
|
||||||
$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir
|
|
||||||
|
|
||||||
# Next, set the prompt from the command line, or the config file, or
|
|
||||||
# just use the name of the virtual environment folder.
|
|
||||||
if ($Prompt) {
|
|
||||||
Write-Verbose "Prompt specified as argument, using '$Prompt'"
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value"
|
|
||||||
if ($pyvenvCfg -and $pyvenvCfg['prompt']) {
|
|
||||||
Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'"
|
|
||||||
$Prompt = $pyvenvCfg['prompt'];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)"
|
|
||||||
Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'"
|
|
||||||
$Prompt = Split-Path -Path $venvDir -Leaf
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Verbose "Prompt = '$Prompt'"
|
|
||||||
Write-Verbose "VenvDir='$VenvDir'"
|
|
||||||
|
|
||||||
# Deactivate any currently active virtual environment, but leave the
|
|
||||||
# deactivate function in place.
|
|
||||||
deactivate -nondestructive
|
|
||||||
|
|
||||||
# Now set the environment variable VIRTUAL_ENV, used by many tools to determine
|
|
||||||
# that there is an activated venv.
|
|
||||||
$env:VIRTUAL_ENV = $VenvDir
|
|
||||||
|
|
||||||
if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) {
|
|
||||||
|
|
||||||
Write-Verbose "Setting prompt to '$Prompt'"
|
|
||||||
|
|
||||||
# Set the prompt to include the env name
|
|
||||||
# Make sure _OLD_VIRTUAL_PROMPT is global
|
|
||||||
function global:_OLD_VIRTUAL_PROMPT { "" }
|
|
||||||
Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT
|
|
||||||
New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt
|
|
||||||
|
|
||||||
function global:prompt {
|
|
||||||
Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) "
|
|
||||||
_OLD_VIRTUAL_PROMPT
|
|
||||||
}
|
|
||||||
$env:VIRTUAL_ENV_PROMPT = $Prompt
|
|
||||||
}
|
|
||||||
|
|
||||||
# Clear PYTHONHOME
|
|
||||||
if (Test-Path -Path Env:PYTHONHOME) {
|
|
||||||
Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME
|
|
||||||
Remove-Item -Path Env:PYTHONHOME
|
|
||||||
}
|
|
||||||
|
|
||||||
# Add the venv to the PATH
|
|
||||||
Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH
|
|
||||||
$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH"
|
|
|
@ -1,69 +0,0 @@
|
||||||
# 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 2> /dev/null
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
|
|
||||||
PS1="${_OLD_VIRTUAL_PS1:-}"
|
|
||||||
export PS1
|
|
||||||
unset _OLD_VIRTUAL_PS1
|
|
||||||
fi
|
|
||||||
|
|
||||||
unset VIRTUAL_ENV
|
|
||||||
unset VIRTUAL_ENV_PROMPT
|
|
||||||
if [ ! "${1:-}" = "nondestructive" ] ; then
|
|
||||||
# Self destruct!
|
|
||||||
unset -f deactivate
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# unset irrelevant variables
|
|
||||||
deactivate nondestructive
|
|
||||||
|
|
||||||
VIRTUAL_ENV="/home/runner/work/zwave-js-ui_ynh/zwave-js-ui_ynh/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:-}"
|
|
||||||
PS1="(venv) ${PS1:-}"
|
|
||||||
export PS1
|
|
||||||
VIRTUAL_ENV_PROMPT="(venv) "
|
|
||||||
export VIRTUAL_ENV_PROMPT
|
|
||||||
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 2> /dev/null
|
|
||||||
fi
|
|
|
@ -1,95 +0,0 @@
|
||||||
#!/home/runner/work/zwave-js-ui_ynh/zwave-js-ui_ynh/venv/bin/python3
|
|
||||||
# PYTHON_ARGCOMPLETE_OK
|
|
||||||
|
|
||||||
# Copyright 2012-2021, Andrey Kislyuk and argcomplete contributors.
|
|
||||||
# Licensed under the Apache License. See https://github.com/kislyuk/argcomplete for more info.
|
|
||||||
|
|
||||||
"""
|
|
||||||
Activate the generic bash-completion script for the argcomplete module.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import fileinput
|
|
||||||
import os
|
|
||||||
import shutil
|
|
||||||
import sys
|
|
||||||
|
|
||||||
import argcomplete
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
||||||
|
|
||||||
dest_opt = parser.add_argument(
|
|
||||||
"--dest", default="/etc/bash_completion.d", help="Specify the bash completion modules directory to install into"
|
|
||||||
)
|
|
||||||
parser.add_argument("--user", help="Install into user directory (~/.bash_completion.d/)", action="store_true")
|
|
||||||
parser.add_argument(
|
|
||||||
"--no-defaults",
|
|
||||||
dest="use_defaults",
|
|
||||||
action="store_false",
|
|
||||||
default=True,
|
|
||||||
help="When no matches are generated, do not fallback to readline's default completion",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--complete-arguments",
|
|
||||||
nargs=argparse.REMAINDER,
|
|
||||||
help="arguments to call complete with; use of this option discards default options",
|
|
||||||
)
|
|
||||||
argcomplete.autocomplete(parser)
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
if args.user:
|
|
||||||
args.dest = os.path.expanduser("~/.bash_completion.d/")
|
|
||||||
if not os.path.exists(args.dest):
|
|
||||||
try:
|
|
||||||
os.mkdir(args.dest)
|
|
||||||
except Exception as e:
|
|
||||||
parser.error("Path {d} does not exist and could not be created: {e}".format(d=args.dest, e=e))
|
|
||||||
elif not os.path.exists(args.dest) and args.dest != "-":
|
|
||||||
if sys.platform == "darwin" and args.dest == dest_opt.default and os.path.exists("/usr/local" + dest_opt.default):
|
|
||||||
args.dest = "/usr/local" + dest_opt.default
|
|
||||||
else:
|
|
||||||
parser.error("Path {d} does not exist".format(d=args.dest))
|
|
||||||
|
|
||||||
activator = os.path.join(os.path.dirname(argcomplete.__file__), "bash_completion.d", "python-argcomplete")
|
|
||||||
|
|
||||||
if args.complete_arguments is None:
|
|
||||||
complete_options = "-o default -o bashdefault" if args.use_defaults else "-o bashdefault"
|
|
||||||
else:
|
|
||||||
complete_options = " ".join(args.complete_arguments)
|
|
||||||
complete_call = "complete{} -D -F _python_argcomplete_global".format(" " + complete_options if complete_options else "")
|
|
||||||
|
|
||||||
|
|
||||||
def replaceCompleteCall(line):
|
|
||||||
if line.startswith("complete") and "_python_argcomplete_global" in line:
|
|
||||||
return complete_call + ("\n" if line.endswith("\n") else "")
|
|
||||||
else:
|
|
||||||
return line
|
|
||||||
|
|
||||||
|
|
||||||
if args.dest == "-":
|
|
||||||
for line in open(activator):
|
|
||||||
sys.stdout.write(replaceCompleteCall(line))
|
|
||||||
else:
|
|
||||||
dest = os.path.join(args.dest, "python-argcomplete")
|
|
||||||
|
|
||||||
sys.stdout.write("Installing bash completion script " + dest)
|
|
||||||
if not args.use_defaults:
|
|
||||||
sys.stdout.write(" without -o default")
|
|
||||||
elif args.complete_arguments:
|
|
||||||
sys.stdout.write(" with options: " + complete_options)
|
|
||||||
sys.stdout.write("\n")
|
|
||||||
|
|
||||||
try:
|
|
||||||
shutil.copy(activator, dest)
|
|
||||||
if args.complete_arguments or not args.use_defaults:
|
|
||||||
for line in fileinput.input(dest, inplace=True):
|
|
||||||
# fileinput with inplace=True redirects stdout to the edited file
|
|
||||||
sys.stdout.write(replaceCompleteCall(line))
|
|
||||||
except Exception as e:
|
|
||||||
err = str(e)
|
|
||||||
if args.dest == dest_opt.default:
|
|
||||||
err += (
|
|
||||||
"\nPlease try --user to install into a user directory, "
|
|
||||||
"or --dest to specify the bash completion modules directory"
|
|
||||||
)
|
|
||||||
parser.error(err)
|
|
|
@ -1,26 +0,0 @@
|
||||||
# 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; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate'
|
|
||||||
|
|
||||||
# Unset irrelevant variables.
|
|
||||||
deactivate nondestructive
|
|
||||||
|
|
||||||
setenv VIRTUAL_ENV "/home/runner/work/zwave-js-ui_ynh/zwave-js-ui_ynh/venv"
|
|
||||||
|
|
||||||
set _OLD_VIRTUAL_PATH="$PATH"
|
|
||||||
setenv PATH "$VIRTUAL_ENV/bin:$PATH"
|
|
||||||
|
|
||||||
|
|
||||||
set _OLD_VIRTUAL_PROMPT="$prompt"
|
|
||||||
|
|
||||||
if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
|
|
||||||
set prompt = "(venv) $prompt"
|
|
||||||
setenv VIRTUAL_ENV_PROMPT "(venv) "
|
|
||||||
endif
|
|
||||||
|
|
||||||
alias pydoc python -m pydoc
|
|
||||||
|
|
||||||
rehash
|
|
|
@ -1,66 +0,0 @@
|
||||||
# This file must be used with "source <venv>/bin/activate.fish" *from fish*
|
|
||||||
# (https://fishshell.com/); you cannot run it directly.
|
|
||||||
|
|
||||||
function deactivate -d "Exit virtual environment 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
|
|
||||||
set -e VIRTUAL_ENV_PROMPT
|
|
||||||
if test "$argv[1]" != "nondestructive"
|
|
||||||
# Self-destruct!
|
|
||||||
functions -e deactivate
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Unset irrelevant variables.
|
|
||||||
deactivate nondestructive
|
|
||||||
|
|
||||||
set -gx VIRTUAL_ENV "/home/runner/work/zwave-js-ui_ynh/zwave-js-ui_ynh/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
|
|
||||||
|
|
||||||
# Output the venv prompt; color taken from the blue of the Python logo.
|
|
||||||
printf "%s%s%s" (set_color 4B8BBE) "(venv) " (set_color normal)
|
|
||||||
|
|
||||||
# Restore the return status of the previous command.
|
|
||||||
echo "exit $old_status" | .
|
|
||||||
# Output the original/"old" prompt.
|
|
||||||
_old_fish_prompt
|
|
||||||
end
|
|
||||||
|
|
||||||
set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
|
|
||||||
set -gx VIRTUAL_ENV_PROMPT "(venv) "
|
|
||||||
end
|
|
|
@ -1,8 +0,0 @@
|
||||||
#!/home/runner/work/zwave-js-ui_ynh/zwave-js-ui_ynh/venv/bin/python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from pip._internal.cli.main import main
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(main())
|
|
|
@ -1,8 +0,0 @@
|
||||||
#!/home/runner/work/zwave-js-ui_ynh/zwave-js-ui_ynh/venv/bin/python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from pip._internal.cli.main import main
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(main())
|
|
|
@ -1,8 +0,0 @@
|
||||||
#!/home/runner/work/zwave-js-ui_ynh/zwave-js-ui_ynh/venv/bin/python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from pip._internal.cli.main import main
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(main())
|
|
|
@ -1 +0,0 @@
|
||||||
python3
|
|
|
@ -1,63 +0,0 @@
|
||||||
#!/home/runner/work/zwave-js-ui_ynh/zwave-js-ui_ynh/venv/bin/python3
|
|
||||||
|
|
||||||
# Copyright 2012-2021, Andrey Kislyuk and argcomplete contributors.
|
|
||||||
# Licensed under the Apache License. See https://github.com/kislyuk/argcomplete for more info.
|
|
||||||
|
|
||||||
"""
|
|
||||||
This script is part of the Python argcomplete package (https://github.com/kislyuk/argcomplete).
|
|
||||||
It is used to check if an EASY-INSTALL-SCRIPT wrapper redirects to a script that contains the string
|
|
||||||
"PYTHON_ARGCOMPLETE_OK". If you have enabled global completion in argcomplete, the completion hook will run it every
|
|
||||||
time you press <TAB> in your shell.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
python-argcomplete-check-easy-install-script <input executable file>
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
|
|
||||||
if len(sys.argv) != 2:
|
|
||||||
sys.exit(__doc__)
|
|
||||||
|
|
||||||
sys.tracebacklimit = 0
|
|
||||||
|
|
||||||
with open(sys.argv[1]) as fh:
|
|
||||||
line1, head = fh.read(1024).split("\n", 1)[:2]
|
|
||||||
if line1.startswith("#") and ("py" in line1 or "Py" in line1):
|
|
||||||
import re
|
|
||||||
|
|
||||||
lines = head.split("\n", 12)
|
|
||||||
for line in lines:
|
|
||||||
if line.startswith("# EASY-INSTALL-SCRIPT"):
|
|
||||||
import pkg_resources
|
|
||||||
|
|
||||||
dist, script = re.match("# EASY-INSTALL-SCRIPT: '(.+)','(.+)'", line).groups()
|
|
||||||
if "PYTHON_ARGCOMPLETE_OK" in pkg_resources.get_distribution(dist).get_metadata("scripts/" + script):
|
|
||||||
exit(0)
|
|
||||||
elif line.startswith("# EASY-INSTALL-ENTRY-SCRIPT"):
|
|
||||||
dist, group, name = re.match("# EASY-INSTALL-ENTRY-SCRIPT: '(.+)','(.+)','(.+)'", line).groups()
|
|
||||||
import pkgutil
|
|
||||||
|
|
||||||
import pkg_resources
|
|
||||||
|
|
||||||
module_name = pkg_resources.get_distribution(dist).get_entry_info(group, name).module_name
|
|
||||||
with open(pkgutil.get_loader(module_name).get_filename()) as mod_fh:
|
|
||||||
if "PYTHON_ARGCOMPLETE_OK" in mod_fh.read(1024):
|
|
||||||
exit(0)
|
|
||||||
elif line.startswith("# EASY-INSTALL-DEV-SCRIPT"):
|
|
||||||
for line2 in lines:
|
|
||||||
if line2.startswith("__file__"):
|
|
||||||
filename = re.match("__file__ = '(.+)'", line2).group(1)
|
|
||||||
with open(filename) as mod_fh:
|
|
||||||
if "PYTHON_ARGCOMPLETE_OK" in mod_fh.read(1024):
|
|
||||||
exit(0)
|
|
||||||
elif line.startswith("# PBR Generated"):
|
|
||||||
module = re.search("from (.*) import", head).groups()[0]
|
|
||||||
import pkgutil
|
|
||||||
|
|
||||||
import pkg_resources
|
|
||||||
|
|
||||||
with open(pkgutil.get_loader(module).get_filename()) as mod_fh:
|
|
||||||
if "PYTHON_ARGCOMPLETE_OK" in mod_fh.read(1024):
|
|
||||||
exit(0)
|
|
||||||
|
|
||||||
exit(1)
|
|
|
@ -1 +0,0 @@
|
||||||
/usr/bin/python3
|
|
|
@ -1 +0,0 @@
|
||||||
python3
|
|
|
@ -1,68 +0,0 @@
|
||||||
#!/home/runner/work/zwave-js-ui_ynh/zwave-js-ui_ynh/venv/bin/python3
|
|
||||||
# PYTHON_ARGCOMPLETE_OK
|
|
||||||
|
|
||||||
# Copyright 2012-2021, Andrey Kislyuk and argcomplete contributors.
|
|
||||||
# Licensed under the Apache License. See https://github.com/kislyuk/argcomplete for more info.
|
|
||||||
|
|
||||||
"""
|
|
||||||
Register a Python executable for use with the argcomplete module.
|
|
||||||
|
|
||||||
To perform the registration, source the output of this script in your bash shell
|
|
||||||
(quote the output to avoid interpolation).
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
$ eval "$(register-python-argcomplete my-favorite-script.py)"
|
|
||||||
|
|
||||||
For Tcsh
|
|
||||||
|
|
||||||
$ eval `register-python-argcomplete --shell tcsh my-favorite-script.py`
|
|
||||||
|
|
||||||
For Fish
|
|
||||||
|
|
||||||
$ register-python-argcomplete --shell fish my-favourite-script.py > ~/.config/fish/my-favourite-script.py.fish
|
|
||||||
"""
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import sys
|
|
||||||
|
|
||||||
import argcomplete
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
"--no-defaults",
|
|
||||||
dest="use_defaults",
|
|
||||||
action="store_false",
|
|
||||||
default=True,
|
|
||||||
help="When no matches are generated, do not fallback to readline's default completion",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--complete-arguments",
|
|
||||||
nargs=argparse.REMAINDER,
|
|
||||||
help="arguments to call complete with; use of this option discards default options",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-s", "--shell", choices=("bash", "tcsh", "fish", "powershell"), default="bash",
|
|
||||||
help="output code for the specified shell"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-e", "--external-argcomplete-script", help="external argcomplete script for auto completion of the executable"
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument("executable", nargs="+", help="executable to completed (when invoked by exactly this name)")
|
|
||||||
|
|
||||||
argcomplete.autocomplete(parser)
|
|
||||||
|
|
||||||
if len(sys.argv) == 1:
|
|
||||||
parser.print_help()
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
|
|
||||||
sys.stdout.write(
|
|
||||||
argcomplete.shellcode(
|
|
||||||
args.executable, args.use_defaults, args.shell, args.complete_arguments, args.external_argcomplete_script
|
|
||||||
)
|
|
||||||
)
|
|
|
@ -1,8 +0,0 @@
|
||||||
#!/home/runner/work/zwave-js-ui_ynh/zwave-js-ui_ynh/venv/bin/python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from tomlq import main
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(main())
|
|
|
@ -1,8 +0,0 @@
|
||||||
#!/home/runner/work/zwave-js-ui_ynh/zwave-js-ui_ynh/venv/bin/python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from yq import xq_cli
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(xq_cli())
|
|
|
@ -1,8 +0,0 @@
|
||||||
#!/home/runner/work/zwave-js-ui_ynh/zwave-js-ui_ynh/venv/bin/python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from yq import cli
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(cli())
|
|
|
@ -1 +0,0 @@
|
||||||
pip
|
|
|
@ -1,20 +0,0 @@
|
||||||
Copyright (c) 2017-2021 Ingy döt Net
|
|
||||||
Copyright (c) 2006-2016 Kirill Simonov
|
|
||||||
|
|
||||||
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.
|
|
|
@ -1,46 +0,0 @@
|
||||||
Metadata-Version: 2.1
|
|
||||||
Name: PyYAML
|
|
||||||
Version: 6.0
|
|
||||||
Summary: YAML parser and emitter for Python
|
|
||||||
Home-page: https://pyyaml.org/
|
|
||||||
Author: Kirill Simonov
|
|
||||||
Author-email: xi@resolvent.net
|
|
||||||
License: MIT
|
|
||||||
Download-URL: https://pypi.org/project/PyYAML/
|
|
||||||
Project-URL: Bug Tracker, https://github.com/yaml/pyyaml/issues
|
|
||||||
Project-URL: CI, https://github.com/yaml/pyyaml/actions
|
|
||||||
Project-URL: Documentation, https://pyyaml.org/wiki/PyYAMLDocumentation
|
|
||||||
Project-URL: Mailing lists, http://lists.sourceforge.net/lists/listinfo/yaml-core
|
|
||||||
Project-URL: Source Code, https://github.com/yaml/pyyaml
|
|
||||||
Platform: Any
|
|
||||||
Classifier: Development Status :: 5 - Production/Stable
|
|
||||||
Classifier: Intended Audience :: Developers
|
|
||||||
Classifier: License :: OSI Approved :: MIT License
|
|
||||||
Classifier: Operating System :: OS Independent
|
|
||||||
Classifier: Programming Language :: Cython
|
|
||||||
Classifier: Programming Language :: Python
|
|
||||||
Classifier: Programming Language :: Python :: 3
|
|
||||||
Classifier: Programming Language :: Python :: 3.6
|
|
||||||
Classifier: Programming Language :: Python :: 3.7
|
|
||||||
Classifier: Programming Language :: Python :: 3.8
|
|
||||||
Classifier: Programming Language :: Python :: 3.9
|
|
||||||
Classifier: Programming Language :: Python :: 3.10
|
|
||||||
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
||||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
||||||
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
||||||
Classifier: Topic :: Text Processing :: Markup
|
|
||||||
Requires-Python: >=3.6
|
|
||||||
License-File: LICENSE
|
|
||||||
|
|
||||||
YAML is a data serialization format designed for human readability
|
|
||||||
and interaction with scripting languages. PyYAML is a YAML parser
|
|
||||||
and emitter for Python.
|
|
||||||
|
|
||||||
PyYAML features a complete YAML 1.1 parser, Unicode support, pickle
|
|
||||||
support, capable extension API, and sensible error messages. PyYAML
|
|
||||||
supports standard YAML tags and provides Python-specific tags that
|
|
||||||
allow to represent an arbitrary Python object.
|
|
||||||
|
|
||||||
PyYAML is applicable for a broad range of tasks from complex
|
|
||||||
configuration files to object serialization and persistence.
|
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
PyYAML-6.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
|
||||||
PyYAML-6.0.dist-info/LICENSE,sha256=jTko-dxEkP1jVwfLiOsmvXZBAqcoKVQwfT5RZ6V36KQ,1101
|
|
||||||
PyYAML-6.0.dist-info/METADATA,sha256=QmHx9kGp_0yezQCXYaft4eEFeJ6W4oyFfYwHDLP1kdg,2006
|
|
||||||
PyYAML-6.0.dist-info/RECORD,,
|
|
||||||
PyYAML-6.0.dist-info/WHEEL,sha256=gn97LKY8yapDq5WrPM24dc0-MAPY7hOCxJGLaGdibEk,225
|
|
||||||
PyYAML-6.0.dist-info/top_level.txt,sha256=rpj0IVMTisAjh_1vG3Ccf9v5jpCQwAz6cD1IVU5ZdhQ,11
|
|
||||||
_yaml/__init__.py,sha256=04Ae_5osxahpJHa3XBZUAf4wi6XX32gR8D6X6p64GEA,1402
|
|
||||||
_yaml/__pycache__/__init__.cpython-310.pyc,,
|
|
||||||
yaml/__init__.py,sha256=NDS7S8XgA72-hY6LRmGzUWTPvzGzjWVrWk-OGA-77AA,12309
|
|
||||||
yaml/__pycache__/__init__.cpython-310.pyc,,
|
|
||||||
yaml/__pycache__/composer.cpython-310.pyc,,
|
|
||||||
yaml/__pycache__/constructor.cpython-310.pyc,,
|
|
||||||
yaml/__pycache__/cyaml.cpython-310.pyc,,
|
|
||||||
yaml/__pycache__/dumper.cpython-310.pyc,,
|
|
||||||
yaml/__pycache__/emitter.cpython-310.pyc,,
|
|
||||||
yaml/__pycache__/error.cpython-310.pyc,,
|
|
||||||
yaml/__pycache__/events.cpython-310.pyc,,
|
|
||||||
yaml/__pycache__/loader.cpython-310.pyc,,
|
|
||||||
yaml/__pycache__/nodes.cpython-310.pyc,,
|
|
||||||
yaml/__pycache__/parser.cpython-310.pyc,,
|
|
||||||
yaml/__pycache__/reader.cpython-310.pyc,,
|
|
||||||
yaml/__pycache__/representer.cpython-310.pyc,,
|
|
||||||
yaml/__pycache__/resolver.cpython-310.pyc,,
|
|
||||||
yaml/__pycache__/scanner.cpython-310.pyc,,
|
|
||||||
yaml/__pycache__/serializer.cpython-310.pyc,,
|
|
||||||
yaml/__pycache__/tokens.cpython-310.pyc,,
|
|
||||||
yaml/_yaml.cpython-310-x86_64-linux-gnu.so,sha256=RsHkV2bQtRQWNALoJ8O3V0nq2GceRCbMSh4hIkd4QZQ,2165840
|
|
||||||
yaml/composer.py,sha256=_Ko30Wr6eDWUeUpauUGT3Lcg9QPBnOPVlTnIMRGJ9FM,4883
|
|
||||||
yaml/constructor.py,sha256=kNgkfaeLUkwQYY_Q6Ff1Tz2XVw_pG1xVE9Ak7z-viLA,28639
|
|
||||||
yaml/cyaml.py,sha256=6ZrAG9fAYvdVe2FK_w0hmXoG7ZYsoYUwapG8CiC72H0,3851
|
|
||||||
yaml/dumper.py,sha256=PLctZlYwZLp7XmeUdwRuv4nYOZ2UBnDIUy8-lKfLF-o,2837
|
|
||||||
yaml/emitter.py,sha256=jghtaU7eFwg31bG0B7RZea_29Adi9CKmXq_QjgQpCkQ,43006
|
|
||||||
yaml/error.py,sha256=Ah9z-toHJUbE9j-M8YpxgSRM5CgLCcwVzJgLLRF2Fxo,2533
|
|
||||||
yaml/events.py,sha256=50_TksgQiE4up-lKo_V-nBy-tAIxkIPQxY5qDhKCeHw,2445
|
|
||||||
yaml/loader.py,sha256=UVa-zIqmkFSCIYq_PgSGm4NSJttHY2Rf_zQ4_b1fHN0,2061
|
|
||||||
yaml/nodes.py,sha256=gPKNj8pKCdh2d4gr3gIYINnPOaOxGhJAUiYhGRnPE84,1440
|
|
||||||
yaml/parser.py,sha256=ilWp5vvgoHFGzvOZDItFoGjD6D42nhlZrZyjAwa0oJo,25495
|
|
||||||
yaml/reader.py,sha256=0dmzirOiDG4Xo41RnuQS7K9rkY3xjHiVasfDMNTqCNw,6794
|
|
||||||
yaml/representer.py,sha256=IuWP-cAW9sHKEnS0gCqSa894k1Bg4cgTxaDwIcbRQ-Y,14190
|
|
||||||
yaml/resolver.py,sha256=9L-VYfm4mWHxUD1Vg4X7rjDRK_7VZd6b92wzq7Y2IKY,9004
|
|
||||||
yaml/scanner.py,sha256=YEM3iLZSaQwXcQRg2l2R4MdT0zGP2F9eHkKGKnHyWQY,51279
|
|
||||||
yaml/serializer.py,sha256=ChuFgmhU01hj4xgI8GaKv6vfM2Bujwa9i7d2FAHj7cA,4165
|
|
||||||
yaml/tokens.py,sha256=lTQIzSVw8Mg9wv459-TjiOQe6wVziqaRlqX2_89rp54,2573
|
|
|
@ -1,8 +0,0 @@
|
||||||
Wheel-Version: 1.0
|
|
||||||
Generator: bdist_wheel (0.37.0)
|
|
||||||
Root-Is-Purelib: false
|
|
||||||
Tag: cp310-cp310-manylinux_2_5_x86_64
|
|
||||||
Tag: cp310-cp310-manylinux1_x86_64
|
|
||||||
Tag: cp310-cp310-manylinux_2_12_x86_64
|
|
||||||
Tag: cp310-cp310-manylinux2010_x86_64
|
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
_yaml
|
|
||||||
yaml
|
|
Binary file not shown.
|
@ -1,132 +0,0 @@
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import importlib
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
|
|
||||||
is_pypy = '__pypy__' in sys.builtin_module_names
|
|
||||||
|
|
||||||
|
|
||||||
warnings.filterwarnings('ignore',
|
|
||||||
r'.+ distutils\b.+ deprecated',
|
|
||||||
DeprecationWarning)
|
|
||||||
|
|
||||||
|
|
||||||
def warn_distutils_present():
|
|
||||||
if 'distutils' not in sys.modules:
|
|
||||||
return
|
|
||||||
if is_pypy and sys.version_info < (3, 7):
|
|
||||||
# PyPy for 3.6 unconditionally imports distutils, so bypass the warning
|
|
||||||
# https://foss.heptapod.net/pypy/pypy/-/blob/be829135bc0d758997b3566062999ee8b23872b4/lib-python/3/site.py#L250
|
|
||||||
return
|
|
||||||
warnings.warn(
|
|
||||||
"Distutils was imported before Setuptools, but importing Setuptools "
|
|
||||||
"also replaces the `distutils` module in `sys.modules`. This may lead "
|
|
||||||
"to undesirable behaviors or errors. To avoid these issues, avoid "
|
|
||||||
"using distutils directly, ensure that setuptools is installed in the "
|
|
||||||
"traditional way (e.g. not an editable install), and/or make sure "
|
|
||||||
"that setuptools is always imported before distutils.")
|
|
||||||
|
|
||||||
|
|
||||||
def clear_distutils():
|
|
||||||
if 'distutils' not in sys.modules:
|
|
||||||
return
|
|
||||||
warnings.warn("Setuptools is replacing distutils.")
|
|
||||||
mods = [name for name in sys.modules if re.match(r'distutils\b', name)]
|
|
||||||
for name in mods:
|
|
||||||
del sys.modules[name]
|
|
||||||
|
|
||||||
|
|
||||||
def enabled():
|
|
||||||
"""
|
|
||||||
Allow selection of distutils by environment variable.
|
|
||||||
"""
|
|
||||||
which = os.environ.get('SETUPTOOLS_USE_DISTUTILS', 'stdlib')
|
|
||||||
return which == 'local'
|
|
||||||
|
|
||||||
|
|
||||||
def ensure_local_distutils():
|
|
||||||
clear_distutils()
|
|
||||||
|
|
||||||
# With the DistutilsMetaFinder in place,
|
|
||||||
# perform an import to cause distutils to be
|
|
||||||
# loaded from setuptools._distutils. Ref #2906.
|
|
||||||
add_shim()
|
|
||||||
importlib.import_module('distutils')
|
|
||||||
remove_shim()
|
|
||||||
|
|
||||||
# check that submodules load as expected
|
|
||||||
core = importlib.import_module('distutils.core')
|
|
||||||
assert '_distutils' in core.__file__, core.__file__
|
|
||||||
|
|
||||||
|
|
||||||
def do_override():
|
|
||||||
"""
|
|
||||||
Ensure that the local copy of distutils is preferred over stdlib.
|
|
||||||
|
|
||||||
See https://github.com/pypa/setuptools/issues/417#issuecomment-392298401
|
|
||||||
for more motivation.
|
|
||||||
"""
|
|
||||||
if enabled():
|
|
||||||
warn_distutils_present()
|
|
||||||
ensure_local_distutils()
|
|
||||||
|
|
||||||
|
|
||||||
class DistutilsMetaFinder:
|
|
||||||
def find_spec(self, fullname, path, target=None):
|
|
||||||
if path is not None:
|
|
||||||
return
|
|
||||||
|
|
||||||
method_name = 'spec_for_{fullname}'.format(**locals())
|
|
||||||
method = getattr(self, method_name, lambda: None)
|
|
||||||
return method()
|
|
||||||
|
|
||||||
def spec_for_distutils(self):
|
|
||||||
import importlib.abc
|
|
||||||
import importlib.util
|
|
||||||
|
|
||||||
class DistutilsLoader(importlib.abc.Loader):
|
|
||||||
|
|
||||||
def create_module(self, spec):
|
|
||||||
return importlib.import_module('setuptools._distutils')
|
|
||||||
|
|
||||||
def exec_module(self, module):
|
|
||||||
pass
|
|
||||||
|
|
||||||
return importlib.util.spec_from_loader('distutils', DistutilsLoader())
|
|
||||||
|
|
||||||
def spec_for_pip(self):
|
|
||||||
"""
|
|
||||||
Ensure stdlib distutils when running under pip.
|
|
||||||
See pypa/pip#8761 for rationale.
|
|
||||||
"""
|
|
||||||
if self.pip_imported_during_build():
|
|
||||||
return
|
|
||||||
clear_distutils()
|
|
||||||
self.spec_for_distutils = lambda: None
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def pip_imported_during_build():
|
|
||||||
"""
|
|
||||||
Detect if pip is being imported in a build script. Ref #2355.
|
|
||||||
"""
|
|
||||||
import traceback
|
|
||||||
return any(
|
|
||||||
frame.f_globals['__file__'].endswith('setup.py')
|
|
||||||
for frame, line in traceback.walk_stack(None)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
DISTUTILS_FINDER = DistutilsMetaFinder()
|
|
||||||
|
|
||||||
|
|
||||||
def add_shim():
|
|
||||||
sys.meta_path.insert(0, DISTUTILS_FINDER)
|
|
||||||
|
|
||||||
|
|
||||||
def remove_shim():
|
|
||||||
try:
|
|
||||||
sys.meta_path.remove(DISTUTILS_FINDER)
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
Binary file not shown.
Binary file not shown.
|
@ -1 +0,0 @@
|
||||||
__import__('_distutils_hack').do_override()
|
|
|
@ -1,33 +0,0 @@
|
||||||
# This is a stub package designed to roughly emulate the _yaml
|
|
||||||
# extension module, which previously existed as a standalone module
|
|
||||||
# and has been moved into the `yaml` package namespace.
|
|
||||||
# It does not perfectly mimic its old counterpart, but should get
|
|
||||||
# close enough for anyone who's relying on it even when they shouldn't.
|
|
||||||
import yaml
|
|
||||||
|
|
||||||
# in some circumstances, the yaml module we imoprted may be from a different version, so we need
|
|
||||||
# to tread carefully when poking at it here (it may not have the attributes we expect)
|
|
||||||
if not getattr(yaml, '__with_libyaml__', False):
|
|
||||||
from sys import version_info
|
|
||||||
|
|
||||||
exc = ModuleNotFoundError if version_info >= (3, 6) else ImportError
|
|
||||||
raise exc("No module named '_yaml'")
|
|
||||||
else:
|
|
||||||
from yaml._yaml import *
|
|
||||||
import warnings
|
|
||||||
warnings.warn(
|
|
||||||
'The _yaml extension module is now located at yaml._yaml'
|
|
||||||
' and its location is subject to change. To use the'
|
|
||||||
' LibYAML-based parser and emitter, import from `yaml`:'
|
|
||||||
' `from yaml import CLoader as Loader, CDumper as Dumper`.',
|
|
||||||
DeprecationWarning
|
|
||||||
)
|
|
||||||
del warnings
|
|
||||||
# Don't `del yaml` here because yaml is actually an existing
|
|
||||||
# namespace member of _yaml.
|
|
||||||
|
|
||||||
__name__ = '_yaml'
|
|
||||||
# If the module is top-level (i.e. not a part of any specific package)
|
|
||||||
# then the attribute should be set to ''.
|
|
||||||
# https://docs.python.org/3.8/library/types.html
|
|
||||||
__package__ = ''
|
|
Binary file not shown.
|
@ -1 +0,0 @@
|
||||||
pip
|
|
|
@ -1,177 +0,0 @@
|
||||||
|
|
||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
|
@ -1,375 +0,0 @@
|
||||||
Metadata-Version: 2.1
|
|
||||||
Name: argcomplete
|
|
||||||
Version: 2.1.1
|
|
||||||
Summary: Bash tab completion for argparse
|
|
||||||
Home-page: https://github.com/kislyuk/argcomplete
|
|
||||||
Author: Andrey Kislyuk
|
|
||||||
Author-email: kislyuk@gmail.com
|
|
||||||
License: Apache Software License
|
|
||||||
Project-URL: Documentation, https://kislyuk.github.io/argcomplete
|
|
||||||
Project-URL: Source Code, https://github.com/kislyuk/argcomplete
|
|
||||||
Project-URL: Issue Tracker, https://github.com/kislyuk/argcomplete/issues
|
|
||||||
Project-URL: Change Log, https://github.com/kislyuk/argcomplete/blob/master/Changes.rst
|
|
||||||
Platform: MacOS X
|
|
||||||
Platform: Posix
|
|
||||||
Classifier: Environment :: Console
|
|
||||||
Classifier: Intended Audience :: Developers
|
|
||||||
Classifier: License :: OSI Approved :: Apache Software License
|
|
||||||
Classifier: Operating System :: MacOS :: MacOS X
|
|
||||||
Classifier: Operating System :: POSIX
|
|
||||||
Classifier: Programming Language :: Python
|
|
||||||
Classifier: Programming Language :: Python :: 3
|
|
||||||
Classifier: Programming Language :: Python :: 3.6
|
|
||||||
Classifier: Programming Language :: Python :: 3.7
|
|
||||||
Classifier: Programming Language :: Python :: 3.8
|
|
||||||
Classifier: Programming Language :: Python :: 3.9
|
|
||||||
Classifier: Programming Language :: Python :: 3.10
|
|
||||||
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
||||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
||||||
Classifier: Development Status :: 5 - Production/Stable
|
|
||||||
Classifier: Topic :: Software Development
|
|
||||||
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
||||||
Classifier: Topic :: System :: Shells
|
|
||||||
Classifier: Topic :: Terminals
|
|
||||||
Requires-Python: >=3.6
|
|
||||||
License-File: LICENSE.rst
|
|
||||||
Requires-Dist: importlib-metadata (<6,>=0.23) ; python_version == "3.6"
|
|
||||||
Requires-Dist: importlib-metadata (<6,>=0.23) ; python_version == "3.7"
|
|
||||||
Provides-Extra: lint
|
|
||||||
Requires-Dist: flake8 ; extra == 'lint'
|
|
||||||
Requires-Dist: mypy ; extra == 'lint'
|
|
||||||
Provides-Extra: test
|
|
||||||
Requires-Dist: coverage ; extra == 'test'
|
|
||||||
Requires-Dist: pexpect ; extra == 'test'
|
|
||||||
Requires-Dist: wheel ; extra == 'test'
|
|
||||||
Requires-Dist: flake8 ; extra == 'test'
|
|
||||||
Requires-Dist: mypy ; extra == 'test'
|
|
||||||
|
|
||||||
argcomplete - Bash/zsh tab completion for argparse
|
|
||||||
==================================================
|
|
||||||
*Tab complete all the things!*
|
|
||||||
|
|
||||||
Argcomplete provides easy, extensible command line tab completion of arguments for your Python script.
|
|
||||||
|
|
||||||
It makes two assumptions:
|
|
||||||
|
|
||||||
* You're using bash or zsh as your shell
|
|
||||||
* You're using `argparse <http://docs.python.org/3/library/argparse.html>`_ to manage your command line arguments/options
|
|
||||||
|
|
||||||
Argcomplete is particularly useful if your program has lots of options or subparsers, and if your program can
|
|
||||||
dynamically suggest completions for your argument/option values (for example, if the user is browsing resources over
|
|
||||||
the network).
|
|
||||||
|
|
||||||
Installation
|
|
||||||
------------
|
|
||||||
::
|
|
||||||
|
|
||||||
pip3 install argcomplete
|
|
||||||
activate-global-python-argcomplete
|
|
||||||
|
|
||||||
See `Activating global completion`_ below for details about the second step (or if it reports an error).
|
|
||||||
|
|
||||||
Refresh your bash environment (start a new shell or ``source /etc/profile``).
|
|
||||||
|
|
||||||
Synopsis
|
|
||||||
--------
|
|
||||||
Python code (e.g. ``my-awesome-script``):
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
#!/usr/bin/env python
|
|
||||||
# PYTHON_ARGCOMPLETE_OK
|
|
||||||
import argcomplete, argparse
|
|
||||||
parser = argparse.ArgumentParser()
|
|
||||||
...
|
|
||||||
argcomplete.autocomplete(parser)
|
|
||||||
args = parser.parse_args()
|
|
||||||
...
|
|
||||||
|
|
||||||
Shellcode (only necessary if global completion is not activated - see `Global completion`_ below), to be put in e.g. ``.bashrc``::
|
|
||||||
|
|
||||||
eval "$(register-python-argcomplete my-awesome-script)"
|
|
||||||
|
|
||||||
argcomplete.autocomplete(*parser*)
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
This method is the entry point to the module. It must be called **after** ArgumentParser construction is complete, but
|
|
||||||
**before** the ``ArgumentParser.parse_args()`` method is called. The method looks for an environment variable that the
|
|
||||||
completion hook shellcode sets, and if it's there, collects completions, prints them to the output stream (fd 8 by
|
|
||||||
default), and exits. Otherwise, it returns to the caller immediately.
|
|
||||||
|
|
||||||
.. admonition:: Side effects
|
|
||||||
|
|
||||||
Argcomplete gets completions by running your program. It intercepts the execution flow at the moment
|
|
||||||
``argcomplete.autocomplete()`` is called. After sending completions, it exits using ``exit_method`` (``os._exit``
|
|
||||||
by default). This means if your program has any side effects that happen before ``argcomplete`` is called, those
|
|
||||||
side effects will happen every time the user presses ``<TAB>`` (although anything your program prints to stdout or
|
|
||||||
stderr will be suppressed). For this reason it's best to construct the argument parser and call
|
|
||||||
``argcomplete.autocomplete()`` as early as possible in your execution flow.
|
|
||||||
|
|
||||||
.. admonition:: Performance
|
|
||||||
|
|
||||||
If the program takes a long time to get to the point where ``argcomplete.autocomplete()`` is called, the tab completion
|
|
||||||
process will feel sluggish, and the user may lose confidence in it. So it's also important to minimize the startup time
|
|
||||||
of the program up to that point (for example, by deferring initialization or importing of large modules until after
|
|
||||||
parsing options).
|
|
||||||
|
|
||||||
Specifying completers
|
|
||||||
---------------------
|
|
||||||
You can specify custom completion functions for your options and arguments. Two styles are supported: callable and
|
|
||||||
readline-style. Callable completers are simpler. They are called with the following keyword arguments:
|
|
||||||
|
|
||||||
* ``prefix``: The prefix text of the last word before the cursor on the command line.
|
|
||||||
For dynamic completers, this can be used to reduce the work required to generate possible completions.
|
|
||||||
* ``action``: The ``argparse.Action`` instance that this completer was called for.
|
|
||||||
* ``parser``: The ``argparse.ArgumentParser`` instance that the action was taken by.
|
|
||||||
* ``parsed_args``: The result of argument parsing so far (the ``argparse.Namespace`` args object normally returned by
|
|
||||||
``ArgumentParser.parse_args()``).
|
|
||||||
|
|
||||||
Completers should return their completions as a list of strings. An example completer for names of environment
|
|
||||||
variables might look like this:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
def EnvironCompleter(**kwargs):
|
|
||||||
return os.environ
|
|
||||||
|
|
||||||
To specify a completer for an argument or option, set the ``completer`` attribute of its associated action. An easy
|
|
||||||
way to do this at definition time is:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
from argcomplete.completers import EnvironCompleter
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser()
|
|
||||||
parser.add_argument("--env-var1").completer = EnvironCompleter
|
|
||||||
parser.add_argument("--env-var2").completer = EnvironCompleter
|
|
||||||
argcomplete.autocomplete(parser)
|
|
||||||
|
|
||||||
If you specify the ``choices`` keyword for an argparse option or argument (and don't specify a completer), it will be
|
|
||||||
used for completions.
|
|
||||||
|
|
||||||
A completer that is initialized with a set of all possible choices of values for its action might look like this:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
class ChoicesCompleter(object):
|
|
||||||
def __init__(self, choices):
|
|
||||||
self.choices = choices
|
|
||||||
|
|
||||||
def __call__(self, **kwargs):
|
|
||||||
return self.choices
|
|
||||||
|
|
||||||
The following two ways to specify a static set of choices are equivalent for completion purposes:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
from argcomplete.completers import ChoicesCompleter
|
|
||||||
|
|
||||||
parser.add_argument("--protocol", choices=('http', 'https', 'ssh', 'rsync', 'wss'))
|
|
||||||
parser.add_argument("--proto").completer=ChoicesCompleter(('http', 'https', 'ssh', 'rsync', 'wss'))
|
|
||||||
|
|
||||||
Note that if you use the ``choices=<completions>`` option, argparse will show
|
|
||||||
all these choices in the ``--help`` output by default. To prevent this, set
|
|
||||||
``metavar`` (like ``parser.add_argument("--protocol", metavar="PROTOCOL",
|
|
||||||
choices=('http', 'https', 'ssh', 'rsync', 'wss'))``).
|
|
||||||
|
|
||||||
The following `script <https://raw.github.com/kislyuk/argcomplete/master/docs/examples/describe_github_user.py>`_ uses
|
|
||||||
``parsed_args`` and `Requests <http://python-requests.org/>`_ to query GitHub for publicly known members of an
|
|
||||||
organization and complete their names, then prints the member description:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
#!/usr/bin/env python
|
|
||||||
# PYTHON_ARGCOMPLETE_OK
|
|
||||||
import argcomplete, argparse, requests, pprint
|
|
||||||
|
|
||||||
def github_org_members(prefix, parsed_args, **kwargs):
|
|
||||||
resource = "https://api.github.com/orgs/{org}/members".format(org=parsed_args.organization)
|
|
||||||
return (member['login'] for member in requests.get(resource).json() if member['login'].startswith(prefix))
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser()
|
|
||||||
parser.add_argument("--organization", help="GitHub organization")
|
|
||||||
parser.add_argument("--member", help="GitHub member").completer = github_org_members
|
|
||||||
|
|
||||||
argcomplete.autocomplete(parser)
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
pprint.pprint(requests.get("https://api.github.com/users/{m}".format(m=args.member)).json())
|
|
||||||
|
|
||||||
`Try it <https://raw.github.com/kislyuk/argcomplete/master/docs/examples/describe_github_user.py>`_ like this::
|
|
||||||
|
|
||||||
./describe_github_user.py --organization heroku --member <TAB>
|
|
||||||
|
|
||||||
If you have a useful completer to add to the `completer library
|
|
||||||
<https://github.com/kislyuk/argcomplete/blob/master/argcomplete/completers.py>`_, send a pull request!
|
|
||||||
|
|
||||||
Readline-style completers
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
The readline_ module defines a completer protocol in rlcompleter_. Readline-style completers are also supported by
|
|
||||||
argcomplete, so you can use the same completer object both in an interactive readline-powered shell and on the bash
|
|
||||||
command line. For example, you can use the readline-style completer provided by IPython_ to get introspective
|
|
||||||
completions like you would get in the IPython shell:
|
|
||||||
|
|
||||||
.. _readline: http://docs.python.org/3/library/readline.html
|
|
||||||
.. _rlcompleter: http://docs.python.org/3/library/rlcompleter.html#completer-objects
|
|
||||||
.. _IPython: http://ipython.org/
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
import IPython
|
|
||||||
parser.add_argument("--python-name").completer = IPython.core.completer.Completer()
|
|
||||||
|
|
||||||
``argcomplete.CompletionFinder.rl_complete`` can also be used to plug in an argparse parser as a readline completer.
|
|
||||||
|
|
||||||
Printing warnings in completers
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
Normal stdout/stderr output is suspended when argcomplete runs. Sometimes, though, when the user presses ``<TAB>``, it's
|
|
||||||
appropriate to print information about why completions generation failed. To do this, use ``warn``:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
from argcomplete import warn
|
|
||||||
|
|
||||||
def AwesomeWebServiceCompleter(prefix, **kwargs):
|
|
||||||
if login_failed:
|
|
||||||
warn("Please log in to Awesome Web Service to use autocompletion")
|
|
||||||
return completions
|
|
||||||
|
|
||||||
Using a custom completion validator
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
By default, argcomplete validates your completions by checking if they start with the prefix given to the completer. You
|
|
||||||
can override this validation check by supplying the ``validator`` keyword to ``argcomplete.autocomplete()``:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
def my_validator(completion_candidate, current_input):
|
|
||||||
"""Complete non-prefix substring matches."""
|
|
||||||
return current_input in completion_candidate
|
|
||||||
|
|
||||||
argcomplete.autocomplete(parser, validator=my_validator)
|
|
||||||
|
|
||||||
Global completion
|
|
||||||
-----------------
|
|
||||||
In global completion mode, you don't have to register each argcomplete-capable executable separately. Instead, bash
|
|
||||||
will look for the string **PYTHON_ARGCOMPLETE_OK** in the first 1024 bytes of any executable that it's running
|
|
||||||
completion for, and if it's found, follow the rest of the argcomplete protocol as described above.
|
|
||||||
|
|
||||||
Additionally, completion is activated for scripts run as ``python <script>`` and ``python -m <module>``.
|
|
||||||
This also works for alternate Python versions (e.g. ``python3`` and ``pypy``), as long as that version of Python has
|
|
||||||
argcomplete installed.
|
|
||||||
|
|
||||||
.. admonition:: Bash version compatibility
|
|
||||||
|
|
||||||
Global completion requires bash support for ``complete -D``, which was introduced in bash 4.2. On OS X or older Linux
|
|
||||||
systems, you will need to update bash to use this feature. Check the version of the running copy of bash with
|
|
||||||
``echo $BASH_VERSION``. On OS X, install bash via `Homebrew <http://brew.sh/>`_ (``brew install bash``), add
|
|
||||||
``/usr/local/bin/bash`` to ``/etc/shells``, and run ``chsh`` to change your shell.
|
|
||||||
|
|
||||||
Global completion is not currently compatible with zsh.
|
|
||||||
|
|
||||||
.. note:: If you use setuptools/distribute ``scripts`` or ``entry_points`` directives to package your module,
|
|
||||||
argcomplete will follow the wrapper scripts to their destination and look for ``PYTHON_ARGCOMPLETE_OK`` in the
|
|
||||||
destination code.
|
|
||||||
|
|
||||||
If you choose not to use global completion, or ship a bash completion module that depends on argcomplete, you must
|
|
||||||
register your script explicitly using ``eval "$(register-python-argcomplete my-awesome-script)"``. Standard bash
|
|
||||||
completion registration roules apply: namely, the script name is passed directly to ``complete``, meaning it is only tab
|
|
||||||
completed when invoked exactly as it was registered. In the above example, ``my-awesome-script`` must be on the path,
|
|
||||||
and the user must be attempting to complete it by that name. The above line alone would **not** allow you to complete
|
|
||||||
``./my-awesome-script``, or ``/path/to/my-awesome-script``.
|
|
||||||
|
|
||||||
Activating global completion
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
The script ``activate-global-python-argcomplete`` will try to install the file
|
|
||||||
``bash_completion.d/python-argcomplete`` (`see on GitHub`_) into an appropriate location on your system
|
|
||||||
(``/etc/bash_completion.d/`` or ``~/.bash_completion.d/``). If it
|
|
||||||
fails, but you know the correct location of your bash completion scripts directory, you can specify it with ``--dest``::
|
|
||||||
|
|
||||||
activate-global-python-argcomplete --dest=/path/to/bash_completion.d
|
|
||||||
|
|
||||||
Otherwise, you can redirect its shellcode output into a file::
|
|
||||||
|
|
||||||
activate-global-python-argcomplete --dest=- > file
|
|
||||||
|
|
||||||
The file's contents should then be sourced in e.g. ``~/.bashrc``.
|
|
||||||
|
|
||||||
.. _`see on GitHub`: https://github.com/kislyuk/argcomplete/blob/master/argcomplete/bash_completion.d/python-argcomplete
|
|
||||||
|
|
||||||
Zsh Support
|
|
||||||
------------
|
|
||||||
To activate completions for zsh you need to have ``bashcompinit`` enabled in zsh::
|
|
||||||
|
|
||||||
autoload -U bashcompinit
|
|
||||||
bashcompinit
|
|
||||||
|
|
||||||
Afterwards you can enable completion for your scripts with ``register-python-argcomplete``::
|
|
||||||
|
|
||||||
eval "$(register-python-argcomplete my-awesome-script)"
|
|
||||||
|
|
||||||
External argcomplete script
|
|
||||||
---------------------------
|
|
||||||
To register an argcomplete script for an arbitrary name, the ``--external-argcomplete-script`` argument of the ``register-python-argcomplete`` script can be used::
|
|
||||||
|
|
||||||
eval "$(register-python-argcomplete --external-argcomplete-script /path/to/script arbitrary-name)"
|
|
||||||
|
|
||||||
This allows, for example, to use the auto completion functionality of argcomplete for an application not written in Python.
|
|
||||||
The command line interface of this program must be additionally implemented in a Python script with argparse and argcomplete and whenever the application is called the registered external argcomplete script is used for auto completion.
|
|
||||||
|
|
||||||
This option can also be used in combination with the other supported shells.
|
|
||||||
|
|
||||||
Python Support
|
|
||||||
--------------
|
|
||||||
Argcomplete requires Python 3.7+.
|
|
||||||
|
|
||||||
Support for other shells
|
|
||||||
------------------------
|
|
||||||
Argcomplete authors provide support only for the bash and zsh shells on Linux and MacOS. For resources related to other
|
|
||||||
shells and platforms, please see the `contrib <https://github.com/kislyuk/argcomplete/tree/develop/contrib>`_ directory.
|
|
||||||
|
|
||||||
Common Problems
|
|
||||||
---------------
|
|
||||||
If global completion is not completing your script, bash may have registered a
|
|
||||||
default completion function::
|
|
||||||
|
|
||||||
$ complete | grep my-awesome-script
|
|
||||||
complete -F _minimal my-awesome-script
|
|
||||||
|
|
||||||
You can fix this by restarting your shell, or by running
|
|
||||||
``complete -r my-awesome-script``.
|
|
||||||
|
|
||||||
Debugging
|
|
||||||
---------
|
|
||||||
Set the ``_ARC_DEBUG`` variable in your shell to enable verbose debug output every time argcomplete runs. This will
|
|
||||||
disrupt the command line composition state of your terminal, but make it possible to see the internal state of the
|
|
||||||
completer if it encounters problems.
|
|
||||||
|
|
||||||
Acknowledgments
|
|
||||||
---------------
|
|
||||||
Inspired and informed by the optcomplete_ module by Martin Blais.
|
|
||||||
|
|
||||||
.. _optcomplete: http://pypi.python.org/pypi/optcomplete
|
|
||||||
|
|
||||||
Links
|
|
||||||
-----
|
|
||||||
* `Project home page (GitHub) <https://github.com/kislyuk/argcomplete>`_
|
|
||||||
* `Documentation <https://kislyuk.github.io/argcomplete/>`_
|
|
||||||
* `Package distribution (PyPI) <https://pypi.python.org/pypi/argcomplete>`_
|
|
||||||
* `Change log <https://github.com/kislyuk/argcomplete/blob/master/Changes.rst>`_
|
|
||||||
* `xontrib-argcomplete <https://github.com/anki-code/xontrib-argcomplete>`_ - support argcomplete in `xonsh <https://github.com/xonsh/xonsh>`_ shell
|
|
||||||
|
|
||||||
Bugs
|
|
||||||
~~~~
|
|
||||||
Please report bugs, issues, feature requests, etc. on `GitHub <https://github.com/kislyuk/argcomplete/issues>`_.
|
|
||||||
|
|
||||||
License
|
|
||||||
-------
|
|
||||||
Licensed under the terms of the `Apache License, Version 2.0 <http://www.apache.org/licenses/LICENSE-2.0>`_.
|
|
||||||
|
|
||||||
.. image:: https://github.com/kislyuk/argcomplete/workflows/Python%20package/badge.svg
|
|
||||||
:target: https://github.com/kislyuk/argcomplete/actions
|
|
||||||
.. image:: https://codecov.io/github/kislyuk/argcomplete/coverage.svg?branch=master
|
|
||||||
:target: https://codecov.io/github/kislyuk/argcomplete?branch=master
|
|
||||||
.. image:: https://img.shields.io/pypi/v/argcomplete.svg
|
|
||||||
:target: https://pypi.python.org/pypi/argcomplete
|
|
||||||
.. image:: https://img.shields.io/pypi/l/argcomplete.svg
|
|
||||||
:target: https://pypi.python.org/pypi/argcomplete
|
|
|
@ -1,24 +0,0 @@
|
||||||
../../../bin/activate-global-python-argcomplete,sha256=JpmrqjohuhcZrEOvsHhZLl_sqc3XDZ0fx-PTBFY1c84,3460
|
|
||||||
../../../bin/python-argcomplete-check-easy-install-script,sha256=LJx0kqmGQUAHEuDt6jP8yCzgAz3tHkAQti3tzHZr6NU,2595
|
|
||||||
../../../bin/register-python-argcomplete,sha256=df_1T3WG5jCdlvM_ZHYKG4rQM2HQqNXqKQVCVI5xn18,1923
|
|
||||||
argcomplete-2.1.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
|
||||||
argcomplete-2.1.1.dist-info/LICENSE.rst,sha256=DVQuDIgE45qn836wDaWnYhSdxoLXgpRRKH4RuTjpRZQ,10174
|
|
||||||
argcomplete-2.1.1.dist-info/METADATA,sha256=qmLkEXNGAspN0R8widpcVwo3UYQTgwOpcxBP7KpCfs4,17263
|
|
||||||
argcomplete-2.1.1.dist-info/RECORD,,
|
|
||||||
argcomplete-2.1.1.dist-info/WHEEL,sha256=2wepM1nk4DS4eFpYrW1TTqPcoGNfHhhO_i5m4cOimbo,92
|
|
||||||
argcomplete-2.1.1.dist-info/top_level.txt,sha256=iwsGO5-UxEhB809OsWbOmLjd8r8X1xtXY65mP01Ahlg,12
|
|
||||||
argcomplete/__init__.py,sha256=yFvTmqH-3ibJTjlHUFvketfrmJvsZLQCch0CXocFsdY,30894
|
|
||||||
argcomplete/__pycache__/__init__.cpython-310.pyc,,
|
|
||||||
argcomplete/__pycache__/_check_console_script.cpython-310.pyc,,
|
|
||||||
argcomplete/__pycache__/_check_module.cpython-310.pyc,,
|
|
||||||
argcomplete/__pycache__/completers.cpython-310.pyc,,
|
|
||||||
argcomplete/__pycache__/my_argparse.cpython-310.pyc,,
|
|
||||||
argcomplete/__pycache__/my_shlex.cpython-310.pyc,,
|
|
||||||
argcomplete/__pycache__/shell_integration.cpython-310.pyc,,
|
|
||||||
argcomplete/_check_console_script.py,sha256=skTEultDVWkTrXaP5-K4GrwqkLTjHPGAuQup4prVKLU,2140
|
|
||||||
argcomplete/_check_module.py,sha256=fAy06Y7mUMPebMvk3fi2FvZgzt9Gd5LEY8DRL38cwdg,2699
|
|
||||||
argcomplete/bash_completion.d/python-argcomplete,sha256=eCcph8CZBeXvAzO4XlHFtmvUoJyTlXgApKH0CFSIn70,4108
|
|
||||||
argcomplete/completers.py,sha256=kIVyxPtHyTZ18EVAe3zXqvNqgCFL_PrJasthMHS6uGw,3589
|
|
||||||
argcomplete/my_argparse.py,sha256=iYbRDef3RbDV5ojKu0a6-sBdQRty1Jjr7X2E-12924Y,15442
|
|
||||||
argcomplete/my_shlex.py,sha256=Y2ecsYGnhMfGe4Od0pWxMPFkZt6w7gsJN8MkkCZkKxI,13248
|
|
||||||
argcomplete/shell_integration.py,sha256=OUE1g6dXcG-q744KkV3UESY0tmvRcrBhjaPW1mqRwu0,6067
|
|
|
@ -1,5 +0,0 @@
|
||||||
Wheel-Version: 1.0
|
|
||||||
Generator: bdist_wheel (0.38.4)
|
|
||||||
Root-Is-Purelib: true
|
|
||||||
Tag: py3-none-any
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
argcomplete
|
|
|
@ -1,722 +0,0 @@
|
||||||
# Copyright 2012-2021, Andrey Kislyuk and argcomplete contributors.
|
|
||||||
# Licensed under the Apache License. See https://github.com/kislyuk/argcomplete for more info.
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import contextlib
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from . import completers
|
|
||||||
from . import my_shlex as shlex
|
|
||||||
from .completers import FilesCompleter, SuppressCompleter
|
|
||||||
from .my_argparse import IntrospectiveArgumentParser, action_is_greedy, action_is_open, action_is_satisfied
|
|
||||||
from .shell_integration import shellcode # noqa
|
|
||||||
|
|
||||||
_DEBUG = "_ARC_DEBUG" in os.environ
|
|
||||||
|
|
||||||
debug_stream = sys.stderr
|
|
||||||
|
|
||||||
|
|
||||||
def debug(*args):
|
|
||||||
if _DEBUG:
|
|
||||||
print(file=debug_stream, *args)
|
|
||||||
|
|
||||||
|
|
||||||
BASH_FILE_COMPLETION_FALLBACK = 79
|
|
||||||
BASH_DIR_COMPLETION_FALLBACK = 80
|
|
||||||
|
|
||||||
safe_actions = (
|
|
||||||
argparse._StoreAction,
|
|
||||||
argparse._StoreConstAction,
|
|
||||||
argparse._StoreTrueAction,
|
|
||||||
argparse._StoreFalseAction,
|
|
||||||
argparse._AppendAction,
|
|
||||||
argparse._AppendConstAction,
|
|
||||||
argparse._CountAction,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def mute_stdout():
|
|
||||||
stdout = sys.stdout
|
|
||||||
sys.stdout = open(os.devnull, "w")
|
|
||||||
try:
|
|
||||||
yield
|
|
||||||
finally:
|
|
||||||
sys.stdout = stdout
|
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def mute_stderr():
|
|
||||||
stderr = sys.stderr
|
|
||||||
sys.stderr = open(os.devnull, "w")
|
|
||||||
try:
|
|
||||||
yield
|
|
||||||
finally:
|
|
||||||
sys.stderr.close()
|
|
||||||
sys.stderr = stderr
|
|
||||||
|
|
||||||
|
|
||||||
class ArgcompleteException(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def split_line(line, point=None):
|
|
||||||
if point is None:
|
|
||||||
point = len(line)
|
|
||||||
line = line[:point]
|
|
||||||
lexer = shlex.shlex(line, posix=True)
|
|
||||||
lexer.whitespace_split = True
|
|
||||||
lexer.wordbreaks = os.environ.get("_ARGCOMPLETE_COMP_WORDBREAKS", "")
|
|
||||||
words = []
|
|
||||||
|
|
||||||
def split_word(word):
|
|
||||||
# TODO: make this less ugly
|
|
||||||
point_in_word = len(word) + point - lexer.instream.tell()
|
|
||||||
if isinstance(lexer.state, (str, bytes)) and lexer.state in lexer.whitespace:
|
|
||||||
point_in_word += 1
|
|
||||||
if point_in_word > len(word):
|
|
||||||
debug("In trailing whitespace")
|
|
||||||
words.append(word)
|
|
||||||
word = ""
|
|
||||||
prefix, suffix = word[:point_in_word], word[point_in_word:]
|
|
||||||
prequote = ""
|
|
||||||
# posix
|
|
||||||
if lexer.state is not None and lexer.state in lexer.quotes:
|
|
||||||
prequote = lexer.state
|
|
||||||
# non-posix
|
|
||||||
# if len(prefix) > 0 and prefix[0] in lexer.quotes:
|
|
||||||
# prequote, prefix = prefix[0], prefix[1:]
|
|
||||||
|
|
||||||
return prequote, prefix, suffix, words, lexer.last_wordbreak_pos
|
|
||||||
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
word = lexer.get_token()
|
|
||||||
if word == lexer.eof:
|
|
||||||
# TODO: check if this is ever unsafe
|
|
||||||
# raise ArgcompleteException("Unexpected end of input")
|
|
||||||
return "", "", "", words, None
|
|
||||||
if lexer.instream.tell() >= point:
|
|
||||||
debug("word", word, "split, lexer state: '{s}'".format(s=lexer.state))
|
|
||||||
return split_word(word)
|
|
||||||
words.append(word)
|
|
||||||
except ValueError:
|
|
||||||
debug("word", lexer.token, "split (lexer stopped, state: '{s}')".format(s=lexer.state))
|
|
||||||
if lexer.instream.tell() >= point:
|
|
||||||
return split_word(lexer.token)
|
|
||||||
else:
|
|
||||||
msg = (
|
|
||||||
"Unexpected internal state. "
|
|
||||||
"Please report this bug at https://github.com/kislyuk/argcomplete/issues."
|
|
||||||
)
|
|
||||||
raise ArgcompleteException(msg)
|
|
||||||
|
|
||||||
|
|
||||||
def default_validator(completion, prefix):
|
|
||||||
return completion.startswith(prefix)
|
|
||||||
|
|
||||||
|
|
||||||
class CompletionFinder(object):
|
|
||||||
"""
|
|
||||||
Inherit from this class if you wish to override any of the stages below. Otherwise, use
|
|
||||||
``argcomplete.autocomplete()`` directly (it's a convenience instance of this class). It has the same signature as
|
|
||||||
:meth:`CompletionFinder.__call__()`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
argument_parser=None,
|
|
||||||
always_complete_options=True,
|
|
||||||
exclude=None,
|
|
||||||
validator=None,
|
|
||||||
print_suppressed=False,
|
|
||||||
default_completer=FilesCompleter(),
|
|
||||||
append_space=None,
|
|
||||||
):
|
|
||||||
self._parser = argument_parser
|
|
||||||
self.always_complete_options = always_complete_options
|
|
||||||
self.exclude = exclude
|
|
||||||
if validator is None:
|
|
||||||
validator = default_validator
|
|
||||||
self.validator = validator
|
|
||||||
self.print_suppressed = print_suppressed
|
|
||||||
self.completing = False
|
|
||||||
self._display_completions = {}
|
|
||||||
self.default_completer = default_completer
|
|
||||||
if append_space is None:
|
|
||||||
append_space = os.environ.get("_ARGCOMPLETE_SUPPRESS_SPACE") != "1"
|
|
||||||
self.append_space = append_space
|
|
||||||
|
|
||||||
def __call__(
|
|
||||||
self,
|
|
||||||
argument_parser,
|
|
||||||
always_complete_options=True,
|
|
||||||
exit_method=os._exit,
|
|
||||||
output_stream=None,
|
|
||||||
exclude=None,
|
|
||||||
validator=None,
|
|
||||||
print_suppressed=False,
|
|
||||||
append_space=None,
|
|
||||||
default_completer=FilesCompleter(),
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
:param argument_parser: The argument parser to autocomplete on
|
|
||||||
:type argument_parser: :class:`argparse.ArgumentParser`
|
|
||||||
:param always_complete_options:
|
|
||||||
Controls the autocompletion of option strings if an option string opening character (normally ``-``) has not
|
|
||||||
been entered. If ``True`` (default), both short (``-x``) and long (``--x``) option strings will be
|
|
||||||
suggested. If ``False``, no option strings will be suggested. If ``long``, long options and short options
|
|
||||||
with no long variant will be suggested. If ``short``, short options and long options with no short variant
|
|
||||||
will be suggested.
|
|
||||||
:type always_complete_options: boolean or string
|
|
||||||
:param exit_method:
|
|
||||||
Method used to stop the program after printing completions. Defaults to :meth:`os._exit`. If you want to
|
|
||||||
perform a normal exit that calls exit handlers, use :meth:`sys.exit`.
|
|
||||||
:type exit_method: callable
|
|
||||||
:param exclude: List of strings representing options to be omitted from autocompletion
|
|
||||||
:type exclude: iterable
|
|
||||||
:param validator:
|
|
||||||
Function to filter all completions through before returning (called with two string arguments, completion
|
|
||||||
and prefix; return value is evaluated as a boolean)
|
|
||||||
:type validator: callable
|
|
||||||
:param print_suppressed:
|
|
||||||
Whether or not to autocomplete options that have the ``help=argparse.SUPPRESS`` keyword argument set.
|
|
||||||
:type print_suppressed: boolean
|
|
||||||
:param append_space:
|
|
||||||
Whether to append a space to unique matches. The default is ``True``.
|
|
||||||
:type append_space: boolean
|
|
||||||
|
|
||||||
.. note::
|
|
||||||
If you are not subclassing CompletionFinder to override its behaviors,
|
|
||||||
use ``argcomplete.autocomplete()`` directly. It has the same signature as this method.
|
|
||||||
|
|
||||||
Produces tab completions for ``argument_parser``. See module docs for more info.
|
|
||||||
|
|
||||||
Argcomplete only executes actions if their class is known not to have side effects. Custom action classes can be
|
|
||||||
added to argcomplete.safe_actions, if their values are wanted in the ``parsed_args`` completer argument, or
|
|
||||||
their execution is otherwise desirable.
|
|
||||||
"""
|
|
||||||
self.__init__(
|
|
||||||
argument_parser,
|
|
||||||
always_complete_options=always_complete_options,
|
|
||||||
exclude=exclude,
|
|
||||||
validator=validator,
|
|
||||||
print_suppressed=print_suppressed,
|
|
||||||
append_space=append_space,
|
|
||||||
default_completer=default_completer,
|
|
||||||
)
|
|
||||||
|
|
||||||
if "_ARGCOMPLETE" not in os.environ:
|
|
||||||
# not an argument completion invocation
|
|
||||||
return
|
|
||||||
|
|
||||||
global debug_stream
|
|
||||||
try:
|
|
||||||
debug_stream = os.fdopen(9, "w")
|
|
||||||
except Exception:
|
|
||||||
debug_stream = sys.stderr
|
|
||||||
debug()
|
|
||||||
|
|
||||||
if output_stream is None:
|
|
||||||
filename = os.environ.get("_ARGCOMPLETE_STDOUT_FILENAME")
|
|
||||||
if filename is not None:
|
|
||||||
debug("Using output file {}".format(filename))
|
|
||||||
output_stream = open(filename, "w")
|
|
||||||
|
|
||||||
if output_stream is None:
|
|
||||||
try:
|
|
||||||
output_stream = os.fdopen(8, "w")
|
|
||||||
except Exception:
|
|
||||||
debug("Unable to open fd 8 for writing, quitting")
|
|
||||||
exit_method(1)
|
|
||||||
|
|
||||||
# print("", stream=debug_stream)
|
|
||||||
# for v in "COMP_CWORD COMP_LINE COMP_POINT COMP_TYPE COMP_KEY _ARGCOMPLETE_COMP_WORDBREAKS COMP_WORDS".split():
|
|
||||||
# print(v, os.environ[v], stream=debug_stream)
|
|
||||||
|
|
||||||
ifs = os.environ.get("_ARGCOMPLETE_IFS", "\013")
|
|
||||||
if len(ifs) != 1:
|
|
||||||
debug("Invalid value for IFS, quitting [{v}]".format(v=ifs))
|
|
||||||
exit_method(1)
|
|
||||||
|
|
||||||
dfs = os.environ.get("_ARGCOMPLETE_DFS")
|
|
||||||
if dfs and len(dfs) != 1:
|
|
||||||
debug("Invalid value for DFS, quitting [{v}]".format(v=dfs))
|
|
||||||
exit_method(1)
|
|
||||||
|
|
||||||
comp_line = os.environ["COMP_LINE"]
|
|
||||||
comp_point = int(os.environ["COMP_POINT"])
|
|
||||||
|
|
||||||
cword_prequote, cword_prefix, cword_suffix, comp_words, last_wordbreak_pos = split_line(comp_line, comp_point)
|
|
||||||
|
|
||||||
# _ARGCOMPLETE is set by the shell script to tell us where comp_words
|
|
||||||
# should start, based on what we're completing.
|
|
||||||
# 1: <script> [args]
|
|
||||||
# 2: python <script> [args]
|
|
||||||
# 3: python -m <module> [args]
|
|
||||||
start = int(os.environ["_ARGCOMPLETE"]) - 1
|
|
||||||
comp_words = comp_words[start:]
|
|
||||||
|
|
||||||
if cword_prefix and cword_prefix[0] in self._parser.prefix_chars and "=" in cword_prefix:
|
|
||||||
# Special case for when the current word is "--optional=PARTIAL_VALUE". Give the optional to the parser.
|
|
||||||
comp_words.append(cword_prefix.split("=", 1)[0])
|
|
||||||
|
|
||||||
debug(
|
|
||||||
"\nLINE: {!r}".format(comp_line),
|
|
||||||
"\nPOINT: {!r}".format(comp_point),
|
|
||||||
"\nPREQUOTE: {!r}".format(cword_prequote),
|
|
||||||
"\nPREFIX: {!r}".format(cword_prefix),
|
|
||||||
"\nSUFFIX: {!r}".format(cword_suffix),
|
|
||||||
"\nWORDS:",
|
|
||||||
comp_words,
|
|
||||||
)
|
|
||||||
|
|
||||||
completions = self._get_completions(comp_words, cword_prefix, cword_prequote, last_wordbreak_pos)
|
|
||||||
|
|
||||||
if dfs:
|
|
||||||
display_completions = {
|
|
||||||
key_part: value.replace(ifs, " ") if value else ""
|
|
||||||
for key, value in self._display_completions.items()
|
|
||||||
for key_part in key
|
|
||||||
}
|
|
||||||
completions = [dfs.join((key, display_completions.get(key) or "")) for key in completions]
|
|
||||||
|
|
||||||
debug("\nReturning completions:", completions)
|
|
||||||
output_stream.write(ifs.join(completions))
|
|
||||||
output_stream.flush()
|
|
||||||
debug_stream.flush()
|
|
||||||
exit_method(0)
|
|
||||||
|
|
||||||
def _get_completions(self, comp_words, cword_prefix, cword_prequote, last_wordbreak_pos):
|
|
||||||
active_parsers = self._patch_argument_parser()
|
|
||||||
|
|
||||||
parsed_args = argparse.Namespace()
|
|
||||||
self.completing = True
|
|
||||||
|
|
||||||
try:
|
|
||||||
debug("invoking parser with", comp_words[1:])
|
|
||||||
with mute_stderr():
|
|
||||||
a = self._parser.parse_known_args(comp_words[1:], namespace=parsed_args)
|
|
||||||
debug("parsed args:", a)
|
|
||||||
except BaseException as e:
|
|
||||||
debug("\nexception", type(e), str(e), "while parsing args")
|
|
||||||
|
|
||||||
self.completing = False
|
|
||||||
|
|
||||||
if "--" in comp_words:
|
|
||||||
self.always_complete_options = False
|
|
||||||
|
|
||||||
completions = self.collect_completions(active_parsers, parsed_args, cword_prefix, debug)
|
|
||||||
completions = self.filter_completions(completions)
|
|
||||||
completions = self.quote_completions(completions, cword_prequote, last_wordbreak_pos)
|
|
||||||
return completions
|
|
||||||
|
|
||||||
def _patch_argument_parser(self):
|
|
||||||
"""
|
|
||||||
Since argparse doesn't support much introspection, we monkey-patch it to replace the parse_known_args method and
|
|
||||||
all actions with hooks that tell us which action was last taken or about to be taken, and let us have the parser
|
|
||||||
figure out which subparsers need to be activated (then recursively monkey-patch those).
|
|
||||||
We save all active ArgumentParsers to extract all their possible option names later.
|
|
||||||
"""
|
|
||||||
self.active_parsers = []
|
|
||||||
self.visited_positionals = []
|
|
||||||
|
|
||||||
completer = self
|
|
||||||
|
|
||||||
def patch(parser):
|
|
||||||
completer.visited_positionals.append(parser)
|
|
||||||
completer.active_parsers.append(parser)
|
|
||||||
|
|
||||||
if isinstance(parser, IntrospectiveArgumentParser):
|
|
||||||
return
|
|
||||||
|
|
||||||
classname = "MonkeyPatchedIntrospectiveArgumentParser"
|
|
||||||
|
|
||||||
parser.__class__ = type(classname, (IntrospectiveArgumentParser, parser.__class__), {})
|
|
||||||
|
|
||||||
for action in parser._actions:
|
|
||||||
if hasattr(action, "_orig_class"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
# TODO: accomplish this with super
|
|
||||||
class IntrospectAction(action.__class__):
|
|
||||||
def __call__(self, parser, namespace, values, option_string=None):
|
|
||||||
debug("Action stub called on", self)
|
|
||||||
debug("\targs:", parser, namespace, values, option_string)
|
|
||||||
debug("\torig class:", self._orig_class)
|
|
||||||
debug("\torig callable:", self._orig_callable)
|
|
||||||
|
|
||||||
if not completer.completing:
|
|
||||||
self._orig_callable(parser, namespace, values, option_string=option_string)
|
|
||||||
elif issubclass(self._orig_class, argparse._SubParsersAction):
|
|
||||||
debug("orig class is a subparsers action: patching and running it")
|
|
||||||
patch(self._name_parser_map[values[0]])
|
|
||||||
self._orig_callable(parser, namespace, values, option_string=option_string)
|
|
||||||
elif self._orig_class in safe_actions:
|
|
||||||
if not self.option_strings:
|
|
||||||
completer.visited_positionals.append(self)
|
|
||||||
|
|
||||||
self._orig_callable(parser, namespace, values, option_string=option_string)
|
|
||||||
|
|
||||||
action._orig_class = action.__class__
|
|
||||||
action._orig_callable = action.__call__
|
|
||||||
action.__class__ = IntrospectAction
|
|
||||||
|
|
||||||
patch(self._parser)
|
|
||||||
|
|
||||||
debug("Active parsers:", self.active_parsers)
|
|
||||||
debug("Visited positionals:", self.visited_positionals)
|
|
||||||
|
|
||||||
return self.active_parsers
|
|
||||||
|
|
||||||
def _get_subparser_completions(self, parser, cword_prefix):
|
|
||||||
def filter_aliases(aliases, prefix):
|
|
||||||
return tuple(x for x in aliases if x.startswith(prefix))
|
|
||||||
|
|
||||||
aliases_by_parser = {}
|
|
||||||
for key in parser.choices.keys():
|
|
||||||
p = parser.choices[key]
|
|
||||||
aliases_by_parser.setdefault(p, []).append(key)
|
|
||||||
|
|
||||||
for action in parser._get_subactions():
|
|
||||||
subcmd_with_aliases = filter_aliases(aliases_by_parser[parser.choices[action.dest]], cword_prefix)
|
|
||||||
if subcmd_with_aliases:
|
|
||||||
self._display_completions[subcmd_with_aliases] = action.help
|
|
||||||
|
|
||||||
completions = [subcmd for subcmd in parser.choices.keys() if subcmd.startswith(cword_prefix)]
|
|
||||||
return completions
|
|
||||||
|
|
||||||
def _include_options(self, action, cword_prefix):
|
|
||||||
if len(cword_prefix) > 0 or self.always_complete_options is True:
|
|
||||||
return [opt for opt in action.option_strings if opt.startswith(cword_prefix)]
|
|
||||||
long_opts = [opt for opt in action.option_strings if len(opt) > 2]
|
|
||||||
short_opts = [opt for opt in action.option_strings if len(opt) <= 2]
|
|
||||||
if self.always_complete_options == "long":
|
|
||||||
return long_opts if long_opts else short_opts
|
|
||||||
elif self.always_complete_options == "short":
|
|
||||||
return short_opts if short_opts else long_opts
|
|
||||||
return []
|
|
||||||
|
|
||||||
def _get_option_completions(self, parser, cword_prefix):
|
|
||||||
self._display_completions.update(
|
|
||||||
[
|
|
||||||
[tuple(x for x in action.option_strings if x.startswith(cword_prefix)), action.help]
|
|
||||||
for action in parser._actions
|
|
||||||
if action.option_strings
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
option_completions = []
|
|
||||||
for action in parser._actions:
|
|
||||||
if not self.print_suppressed:
|
|
||||||
completer = getattr(action, "completer", None)
|
|
||||||
if isinstance(completer, SuppressCompleter) and completer.suppress():
|
|
||||||
continue
|
|
||||||
if action.help == argparse.SUPPRESS:
|
|
||||||
continue
|
|
||||||
if not self._action_allowed(action, parser):
|
|
||||||
continue
|
|
||||||
if not isinstance(action, argparse._SubParsersAction):
|
|
||||||
option_completions += self._include_options(action, cword_prefix)
|
|
||||||
return option_completions
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _action_allowed(action, parser):
|
|
||||||
# Logic adapted from take_action in ArgumentParser._parse_known_args
|
|
||||||
# (members are saved by my_argparse.IntrospectiveArgumentParser)
|
|
||||||
for conflict_action in parser._action_conflicts.get(action, []):
|
|
||||||
if conflict_action in parser._seen_non_default_actions:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _complete_active_option(self, parser, next_positional, cword_prefix, parsed_args, completions):
|
|
||||||
debug("Active actions (L={l}): {a}".format(l=len(parser.active_actions), a=parser.active_actions))
|
|
||||||
|
|
||||||
isoptional = cword_prefix and cword_prefix[0] in parser.prefix_chars
|
|
||||||
optional_prefix = ""
|
|
||||||
greedy_actions = [x for x in parser.active_actions if action_is_greedy(x, isoptional)]
|
|
||||||
if greedy_actions:
|
|
||||||
assert len(greedy_actions) == 1, "expect at most 1 greedy action"
|
|
||||||
# This means the action will fail to parse if the word under the cursor is not given
|
|
||||||
# to it, so give it exclusive control over completions (flush previous completions)
|
|
||||||
debug("Resetting completions because", greedy_actions[0], "must consume the next argument")
|
|
||||||
self._display_completions = {}
|
|
||||||
completions = []
|
|
||||||
elif isoptional:
|
|
||||||
if "=" in cword_prefix:
|
|
||||||
# Special case for when the current word is "--optional=PARTIAL_VALUE".
|
|
||||||
# The completer runs on PARTIAL_VALUE. The prefix is added back to the completions
|
|
||||||
# (and chopped back off later in quote_completions() by the COMP_WORDBREAKS logic).
|
|
||||||
optional_prefix, _, cword_prefix = cword_prefix.partition("=")
|
|
||||||
else:
|
|
||||||
# Only run completers if current word does not start with - (is not an optional)
|
|
||||||
return completions
|
|
||||||
|
|
||||||
complete_remaining_positionals = False
|
|
||||||
# Use the single greedy action (if there is one) or all active actions.
|
|
||||||
for active_action in greedy_actions or parser.active_actions:
|
|
||||||
if not active_action.option_strings: # action is a positional
|
|
||||||
if action_is_open(active_action):
|
|
||||||
# Any positional arguments after this may slide down into this action
|
|
||||||
# if more arguments are added (since the user may not be done yet),
|
|
||||||
# so it is extremely difficult to tell which completers to run.
|
|
||||||
# Running all remaining completers will probably show more than the user wants
|
|
||||||
# but it also guarantees we won't miss anything.
|
|
||||||
complete_remaining_positionals = True
|
|
||||||
if not complete_remaining_positionals:
|
|
||||||
if action_is_satisfied(active_action) and not action_is_open(active_action):
|
|
||||||
debug("Skipping", active_action)
|
|
||||||
continue
|
|
||||||
|
|
||||||
debug("Activating completion for", active_action, active_action._orig_class)
|
|
||||||
# completer = getattr(active_action, "completer", DefaultCompleter())
|
|
||||||
completer = getattr(active_action, "completer", None)
|
|
||||||
|
|
||||||
if completer is None:
|
|
||||||
if active_action.choices is not None and not isinstance(active_action, argparse._SubParsersAction):
|
|
||||||
completer = completers.ChoicesCompleter(active_action.choices)
|
|
||||||
elif not isinstance(active_action, argparse._SubParsersAction):
|
|
||||||
completer = self.default_completer
|
|
||||||
|
|
||||||
if completer:
|
|
||||||
if isinstance(completer, SuppressCompleter) and completer.suppress():
|
|
||||||
continue
|
|
||||||
|
|
||||||
if callable(completer):
|
|
||||||
completions_from_callable = [
|
|
||||||
c
|
|
||||||
for c in completer(
|
|
||||||
prefix=cword_prefix, action=active_action, parser=parser, parsed_args=parsed_args
|
|
||||||
)
|
|
||||||
if self.validator(c, cword_prefix)
|
|
||||||
]
|
|
||||||
|
|
||||||
if completions_from_callable:
|
|
||||||
completions += completions_from_callable
|
|
||||||
if isinstance(completer, completers.ChoicesCompleter):
|
|
||||||
self._display_completions.update(
|
|
||||||
[[(x,), active_action.help] for x in completions_from_callable]
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self._display_completions.update([[(x,), ""] for x in completions_from_callable])
|
|
||||||
else:
|
|
||||||
debug("Completer is not callable, trying the readline completer protocol instead")
|
|
||||||
for i in range(9999):
|
|
||||||
next_completion = completer.complete(cword_prefix, i)
|
|
||||||
if next_completion is None:
|
|
||||||
break
|
|
||||||
if self.validator(next_completion, cword_prefix):
|
|
||||||
self._display_completions.update({(next_completion,): ""})
|
|
||||||
completions.append(next_completion)
|
|
||||||
if optional_prefix:
|
|
||||||
completions = [optional_prefix + "=" + completion for completion in completions]
|
|
||||||
debug("Completions:", completions)
|
|
||||||
return completions
|
|
||||||
|
|
||||||
def collect_completions(self, active_parsers, parsed_args, cword_prefix, debug):
|
|
||||||
"""
|
|
||||||
Visits the active parsers and their actions, executes their completers or introspects them to collect their
|
|
||||||
option strings. Returns the resulting completions as a list of strings.
|
|
||||||
|
|
||||||
This method is exposed for overriding in subclasses; there is no need to use it directly.
|
|
||||||
"""
|
|
||||||
completions = []
|
|
||||||
|
|
||||||
debug("all active parsers:", active_parsers)
|
|
||||||
active_parser = active_parsers[-1]
|
|
||||||
debug("active_parser:", active_parser)
|
|
||||||
if self.always_complete_options or (len(cword_prefix) > 0 and cword_prefix[0] in active_parser.prefix_chars):
|
|
||||||
completions += self._get_option_completions(active_parser, cword_prefix)
|
|
||||||
debug("optional options:", completions)
|
|
||||||
|
|
||||||
next_positional = self._get_next_positional()
|
|
||||||
debug("next_positional:", next_positional)
|
|
||||||
|
|
||||||
if isinstance(next_positional, argparse._SubParsersAction):
|
|
||||||
completions += self._get_subparser_completions(next_positional, cword_prefix)
|
|
||||||
|
|
||||||
completions = self._complete_active_option(
|
|
||||||
active_parser, next_positional, cword_prefix, parsed_args, completions
|
|
||||||
)
|
|
||||||
debug("active options:", completions)
|
|
||||||
debug("display completions:", self._display_completions)
|
|
||||||
|
|
||||||
return completions
|
|
||||||
|
|
||||||
def _get_next_positional(self):
|
|
||||||
"""
|
|
||||||
Get the next positional action if it exists.
|
|
||||||
"""
|
|
||||||
active_parser = self.active_parsers[-1]
|
|
||||||
last_positional = self.visited_positionals[-1]
|
|
||||||
|
|
||||||
all_positionals = active_parser._get_positional_actions()
|
|
||||||
if not all_positionals:
|
|
||||||
return None
|
|
||||||
|
|
||||||
if active_parser == last_positional:
|
|
||||||
return all_positionals[0]
|
|
||||||
|
|
||||||
i = 0
|
|
||||||
for i in range(len(all_positionals)):
|
|
||||||
if all_positionals[i] == last_positional:
|
|
||||||
break
|
|
||||||
|
|
||||||
if i + 1 < len(all_positionals):
|
|
||||||
return all_positionals[i + 1]
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
def filter_completions(self, completions):
|
|
||||||
"""
|
|
||||||
Ensures collected completions are Unicode text, de-duplicates them, and excludes those specified by ``exclude``.
|
|
||||||
Returns the filtered completions as an iterable.
|
|
||||||
|
|
||||||
This method is exposed for overriding in subclasses; there is no need to use it directly.
|
|
||||||
"""
|
|
||||||
# De-duplicate completions and remove excluded ones
|
|
||||||
if self.exclude is None:
|
|
||||||
self.exclude = set()
|
|
||||||
seen = set(self.exclude)
|
|
||||||
return [c for c in completions if c not in seen and not seen.add(c)]
|
|
||||||
|
|
||||||
def quote_completions(self, completions, cword_prequote, last_wordbreak_pos):
|
|
||||||
"""
|
|
||||||
If the word under the cursor started with a quote (as indicated by a nonempty ``cword_prequote``), escapes
|
|
||||||
occurrences of that quote character in the completions, and adds the quote to the beginning of each completion.
|
|
||||||
Otherwise, escapes all characters that bash splits words on (``COMP_WORDBREAKS``), and removes portions of
|
|
||||||
completions before the first colon if (``COMP_WORDBREAKS``) contains a colon.
|
|
||||||
|
|
||||||
If there is only one completion, and it doesn't end with a **continuation character** (``/``, ``:``, or ``=``),
|
|
||||||
adds a space after the completion.
|
|
||||||
|
|
||||||
This method is exposed for overriding in subclasses; there is no need to use it directly.
|
|
||||||
"""
|
|
||||||
special_chars = "\\"
|
|
||||||
# If the word under the cursor was quoted, escape the quote char.
|
|
||||||
# Otherwise, escape all special characters and specially handle all COMP_WORDBREAKS chars.
|
|
||||||
if cword_prequote == "":
|
|
||||||
# Bash mangles completions which contain characters in COMP_WORDBREAKS.
|
|
||||||
# This workaround has the same effect as __ltrim_colon_completions in bash_completion
|
|
||||||
# (extended to characters other than the colon).
|
|
||||||
if last_wordbreak_pos:
|
|
||||||
completions = [c[last_wordbreak_pos + 1 :] for c in completions]
|
|
||||||
special_chars += "();<>|&!`$* \t\n\"'"
|
|
||||||
elif cword_prequote == '"':
|
|
||||||
special_chars += '"`$!'
|
|
||||||
|
|
||||||
if os.environ.get("_ARGCOMPLETE_SHELL") in ("tcsh", "fish"):
|
|
||||||
# tcsh and fish escapes special characters itself.
|
|
||||||
special_chars = ""
|
|
||||||
elif cword_prequote == "'":
|
|
||||||
# Nothing can be escaped in single quotes, so we need to close
|
|
||||||
# the string, escape the single quote, then open a new string.
|
|
||||||
special_chars = ""
|
|
||||||
completions = [c.replace("'", r"'\''") for c in completions]
|
|
||||||
|
|
||||||
for char in special_chars:
|
|
||||||
completions = [c.replace(char, "\\" + char) for c in completions]
|
|
||||||
|
|
||||||
if self.append_space:
|
|
||||||
# Similar functionality in bash was previously turned off by supplying the "-o nospace" option to complete.
|
|
||||||
# Now it is conditionally disabled using "compopt -o nospace" if the match ends in a continuation character.
|
|
||||||
# This code is retained for environments where this isn't done natively.
|
|
||||||
continuation_chars = "=/:"
|
|
||||||
if len(completions) == 1 and completions[0][-1] not in continuation_chars:
|
|
||||||
if cword_prequote == "":
|
|
||||||
completions[0] += " "
|
|
||||||
|
|
||||||
return completions
|
|
||||||
|
|
||||||
def rl_complete(self, text, state):
|
|
||||||
"""
|
|
||||||
Alternate entry point for using the argcomplete completer in a readline-based REPL. See also
|
|
||||||
`rlcompleter <https://docs.python.org/3/library/rlcompleter.html#completer-objects>`_.
|
|
||||||
Usage:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
import argcomplete, argparse, readline
|
|
||||||
parser = argparse.ArgumentParser()
|
|
||||||
...
|
|
||||||
completer = argcomplete.CompletionFinder(parser)
|
|
||||||
readline.set_completer_delims("")
|
|
||||||
readline.set_completer(completer.rl_complete)
|
|
||||||
readline.parse_and_bind("tab: complete")
|
|
||||||
result = input("prompt> ")
|
|
||||||
"""
|
|
||||||
if state == 0:
|
|
||||||
cword_prequote, cword_prefix, cword_suffix, comp_words, first_colon_pos = split_line(text)
|
|
||||||
comp_words.insert(0, sys.argv[0])
|
|
||||||
matches = self._get_completions(comp_words, cword_prefix, cword_prequote, first_colon_pos)
|
|
||||||
self._rl_matches = [text + match[len(cword_prefix) :] for match in matches]
|
|
||||||
|
|
||||||
if state < len(self._rl_matches):
|
|
||||||
return self._rl_matches[state]
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_display_completions(self):
|
|
||||||
"""
|
|
||||||
This function returns a mapping of option names to their help strings for displaying to the user
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
def display_completions(substitution, matches, longest_match_length):
|
|
||||||
_display_completions = argcomplete.autocomplete.get_display_completions()
|
|
||||||
print("")
|
|
||||||
if _display_completions:
|
|
||||||
help_len = [len(x) for x in _display_completions.values() if x]
|
|
||||||
|
|
||||||
if help_len:
|
|
||||||
maxlen = max([len(x) for x in _display_completions])
|
|
||||||
print("\\n".join("{0:{2}} -- {1}".format(k, v, maxlen)
|
|
||||||
for k, v in sorted(_display_completions.items())))
|
|
||||||
else:
|
|
||||||
print(" ".join(k for k in sorted(_display_completions)))
|
|
||||||
else:
|
|
||||||
print(" ".join(x for x in sorted(matches)))
|
|
||||||
|
|
||||||
import readline
|
|
||||||
print("cli /> {0}".format(readline.get_line_buffer()), end="")
|
|
||||||
readline.redisplay()
|
|
||||||
|
|
||||||
...
|
|
||||||
readline.set_completion_display_matches_hook(display_completions)
|
|
||||||
|
|
||||||
"""
|
|
||||||
return {" ".join(k): v for k, v in self._display_completions.items()}
|
|
||||||
|
|
||||||
|
|
||||||
class ExclusiveCompletionFinder(CompletionFinder):
|
|
||||||
@staticmethod
|
|
||||||
def _action_allowed(action, parser):
|
|
||||||
if not CompletionFinder._action_allowed(action, parser):
|
|
||||||
return False
|
|
||||||
|
|
||||||
append_classes = (argparse._AppendAction, argparse._AppendConstAction)
|
|
||||||
if action._orig_class in append_classes:
|
|
||||||
return True
|
|
||||||
|
|
||||||
if action not in parser._seen_non_default_actions:
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
autocomplete = CompletionFinder()
|
|
||||||
autocomplete.__doc__ = """ Use this to access argcomplete. See :meth:`argcomplete.CompletionFinder.__call__()`. """
|
|
||||||
|
|
||||||
|
|
||||||
def warn(*args):
|
|
||||||
"""
|
|
||||||
Prints **args** to standard error when running completions. This will interrupt the user's command line interaction;
|
|
||||||
use it to indicate an error condition that is preventing your completer from working.
|
|
||||||
"""
|
|
||||||
# Don't be tempted to use `print("\n",..., *args)`,
|
|
||||||
# as that will indent **args** by one space character
|
|
||||||
print(file=debug_stream)
|
|
||||||
print(file=debug_stream, *args)
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,57 +0,0 @@
|
||||||
"""
|
|
||||||
Utility for locating the module (or package's __init__.py)
|
|
||||||
associated with a given console_script name
|
|
||||||
and verifying it contains the PYTHON_ARGCOMPLETE_OK marker.
|
|
||||||
|
|
||||||
Such scripts are automatically generated and cannot contain
|
|
||||||
the marker themselves, so we defer to the containing module or package.
|
|
||||||
|
|
||||||
For more information on setuptools console_scripts, see
|
|
||||||
https://setuptools.readthedocs.io/en/latest/setuptools.html#automatic-script-creation
|
|
||||||
|
|
||||||
Intended to be invoked by argcomplete's global completion function.
|
|
||||||
"""
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
try:
|
|
||||||
from importlib.metadata import entry_points as importlib_entry_points
|
|
||||||
except ImportError:
|
|
||||||
from importlib_metadata import entry_points as importlib_entry_points # type:ignore
|
|
||||||
|
|
||||||
from ._check_module import ArgcompleteMarkerNotFound, find
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
# Argument is the full path to the console script.
|
|
||||||
script_path = sys.argv[1]
|
|
||||||
|
|
||||||
# Find the module and function names that correspond to this
|
|
||||||
# assuming it is actually a console script.
|
|
||||||
name = os.path.basename(script_path)
|
|
||||||
entry_points = [ep for ep in importlib_entry_points()["console_scripts"] if ep.name == name]
|
|
||||||
if not entry_points:
|
|
||||||
raise ArgcompleteMarkerNotFound("no entry point found matching script")
|
|
||||||
entry_point = entry_points[0]
|
|
||||||
module_name, function_name = entry_point.value.split(":", 1)
|
|
||||||
|
|
||||||
# Check this looks like the script we really expected.
|
|
||||||
with open(script_path) as f:
|
|
||||||
script = f.read()
|
|
||||||
if "from {} import {}".format(module_name, function_name) not in script:
|
|
||||||
raise ArgcompleteMarkerNotFound("does not appear to be a console script")
|
|
||||||
if "sys.exit({}())".format(function_name) not in script:
|
|
||||||
raise ArgcompleteMarkerNotFound("does not appear to be a console script")
|
|
||||||
|
|
||||||
# Look for the argcomplete marker in the script it imports.
|
|
||||||
with open(find(module_name, return_package=True)) as f:
|
|
||||||
head = f.read(1024)
|
|
||||||
if "PYTHON_ARGCOMPLETE_OK" not in head:
|
|
||||||
raise ArgcompleteMarkerNotFound("marker not found")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
try:
|
|
||||||
main()
|
|
||||||
except ArgcompleteMarkerNotFound as e:
|
|
||||||
sys.exit(str(e))
|
|
|
@ -1,93 +0,0 @@
|
||||||
"""
|
|
||||||
Utility for locating a module (or package's __main__.py) with a given name
|
|
||||||
and verifying it contains the PYTHON_ARGCOMPLETE_OK marker.
|
|
||||||
|
|
||||||
The module name should be specified in a form usable with `python -m`.
|
|
||||||
|
|
||||||
Intended to be invoked by argcomplete's global completion function.
|
|
||||||
"""
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import tokenize
|
|
||||||
|
|
||||||
try:
|
|
||||||
from importlib.util import find_spec # type:ignore
|
|
||||||
except ImportError:
|
|
||||||
import typing as t
|
|
||||||
from collections import namedtuple
|
|
||||||
from imp import find_module
|
|
||||||
|
|
||||||
ModuleSpec = namedtuple("ModuleSpec", ["origin", "has_location", "submodule_search_locations"])
|
|
||||||
|
|
||||||
def find_spec( # type:ignore
|
|
||||||
name: str,
|
|
||||||
package: t.Optional[str] = None,
|
|
||||||
) -> t.Optional[ModuleSpec]:
|
|
||||||
"""Minimal implementation as required by `find`."""
|
|
||||||
try:
|
|
||||||
f, path, _ = find_module(name)
|
|
||||||
except ImportError:
|
|
||||||
return None
|
|
||||||
has_location = path is not None
|
|
||||||
if f is None:
|
|
||||||
return ModuleSpec(None, has_location, [path])
|
|
||||||
f.close()
|
|
||||||
return ModuleSpec(path, has_location, None)
|
|
||||||
|
|
||||||
|
|
||||||
class ArgcompleteMarkerNotFound(RuntimeError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def find(name, return_package=False):
|
|
||||||
names = name.split(".")
|
|
||||||
spec = find_spec(names[0])
|
|
||||||
if spec is None:
|
|
||||||
raise ArgcompleteMarkerNotFound('no module named "{}"'.format(names[0]))
|
|
||||||
if not spec.has_location:
|
|
||||||
raise ArgcompleteMarkerNotFound("cannot locate file")
|
|
||||||
if spec.submodule_search_locations is None:
|
|
||||||
if len(names) != 1:
|
|
||||||
raise ArgcompleteMarkerNotFound("{} is not a package".format(names[0]))
|
|
||||||
return spec.origin
|
|
||||||
if len(spec.submodule_search_locations) != 1:
|
|
||||||
raise ArgcompleteMarkerNotFound("expecting one search location")
|
|
||||||
path = os.path.join(spec.submodule_search_locations[0], *names[1:])
|
|
||||||
if os.path.isdir(path):
|
|
||||||
filename = "__main__.py"
|
|
||||||
if return_package:
|
|
||||||
filename = "__init__.py"
|
|
||||||
return os.path.join(path, filename)
|
|
||||||
else:
|
|
||||||
return path + ".py"
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
try:
|
|
||||||
name = sys.argv[1]
|
|
||||||
except IndexError:
|
|
||||||
raise ArgcompleteMarkerNotFound("missing argument on the command line")
|
|
||||||
|
|
||||||
filename = find(name)
|
|
||||||
if hasattr(tokenize, "open"):
|
|
||||||
open_func = tokenize.open
|
|
||||||
else:
|
|
||||||
open_func = open
|
|
||||||
|
|
||||||
try:
|
|
||||||
fp = open_func(filename)
|
|
||||||
except OSError:
|
|
||||||
raise ArgcompleteMarkerNotFound("cannot open file")
|
|
||||||
|
|
||||||
with fp:
|
|
||||||
head = fp.read(1024)
|
|
||||||
|
|
||||||
if "PYTHON_ARGCOMPLETE_OK" not in head:
|
|
||||||
raise ArgcompleteMarkerNotFound("marker not found")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
try:
|
|
||||||
main()
|
|
||||||
except ArgcompleteMarkerNotFound as e:
|
|
||||||
sys.exit(str(e))
|
|
|
@ -1,106 +0,0 @@
|
||||||
# Copyright 2012-2021, Andrey Kislyuk and argcomplete contributors.
|
|
||||||
# Licensed under the Apache License. See https://github.com/kislyuk/argcomplete for more info.
|
|
||||||
|
|
||||||
# Copy of __expand_tilde_by_ref from bash-completion
|
|
||||||
__python_argcomplete_expand_tilde_by_ref () {
|
|
||||||
if [ "${!1:0:1}" = "~" ]; then
|
|
||||||
if [ "${!1}" != "${!1//\/}" ]; then
|
|
||||||
eval $1="${!1/%\/*}"/'${!1#*/}';
|
|
||||||
else
|
|
||||||
eval $1="${!1}";
|
|
||||||
fi;
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Run something, muting output or redirecting it to the debug stream
|
|
||||||
# depending on the value of _ARC_DEBUG.
|
|
||||||
# If ARGCOMPLETE_USE_TEMPFILES is set, use tempfiles for IPC.
|
|
||||||
__python_argcomplete_run() {
|
|
||||||
if [[ -z "${ARGCOMPLETE_USE_TEMPFILES-}" ]]; then
|
|
||||||
__python_argcomplete_run_inner "$@"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
local tmpfile="$(mktemp)"
|
|
||||||
_ARGCOMPLETE_STDOUT_FILENAME="$tmpfile" __python_argcomplete_run_inner "$@"
|
|
||||||
local code=$?
|
|
||||||
cat "$tmpfile"
|
|
||||||
rm "$tmpfile"
|
|
||||||
return $code
|
|
||||||
}
|
|
||||||
|
|
||||||
__python_argcomplete_run_inner() {
|
|
||||||
if [[ -z "${_ARC_DEBUG-}" ]]; then
|
|
||||||
"$@" 8>&1 9>&2 1>/dev/null 2>&1
|
|
||||||
else
|
|
||||||
"$@" 8>&1 9>&2 1>&9 2>&1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Scan the beginning of an executable file ($1) for a regexp ($2). By default,
|
|
||||||
# scan for the magic string indicating that the executable supports the
|
|
||||||
# argcomplete completion protocol. By default, scan the first kilobyte;
|
|
||||||
# if $3 is set to -n, scan until the first line break up to a kilobyte.
|
|
||||||
__python_argcomplete_scan_head() {
|
|
||||||
read -s -r ${3:--N} 1024 < "$1"
|
|
||||||
[[ "$REPLY" =~ ${2:-PYTHON_ARGCOMPLETE_OK} ]]
|
|
||||||
}
|
|
||||||
|
|
||||||
__python_argcomplete_scan_head_noerr() {
|
|
||||||
__python_argcomplete_scan_head "$@" 2>/dev/null
|
|
||||||
}
|
|
||||||
|
|
||||||
_python_argcomplete_global() {
|
|
||||||
local executable=$1
|
|
||||||
__python_argcomplete_expand_tilde_by_ref executable
|
|
||||||
|
|
||||||
local ARGCOMPLETE=0
|
|
||||||
if [[ "$executable" == python* ]] || [[ "$executable" == pypy* ]]; then
|
|
||||||
if [[ "${COMP_WORDS[1]}" == -m ]]; then
|
|
||||||
if __python_argcomplete_run "$executable" -m argcomplete._check_module "${COMP_WORDS[2]}"; then
|
|
||||||
ARGCOMPLETE=3
|
|
||||||
else
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
elif [[ -f "${COMP_WORDS[1]}" ]] && __python_argcomplete_scan_head_noerr "${COMP_WORDS[1]}"; then
|
|
||||||
local ARGCOMPLETE=2
|
|
||||||
else
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
elif type -P "$executable" >/dev/null 2>&1; then
|
|
||||||
local SCRIPT_NAME=$(type -P "$executable")
|
|
||||||
if (type -t pyenv && [[ "$SCRIPT_NAME" = $(pyenv root)/shims/* ]]) >/dev/null 2>&1; then
|
|
||||||
local SCRIPT_NAME=$(pyenv which "$executable")
|
|
||||||
fi
|
|
||||||
if __python_argcomplete_scan_head_noerr "$SCRIPT_NAME"; then
|
|
||||||
local ARGCOMPLETE=1
|
|
||||||
elif __python_argcomplete_scan_head_noerr "$SCRIPT_NAME" '^#!(.*)$' -n && [[ "${BASH_REMATCH[1]}" =~ ^.*(python|pypy)[0-9\.]*$ ]]; then
|
|
||||||
local interpreter="$BASH_REMATCH"
|
|
||||||
if (__python_argcomplete_scan_head_noerr "$SCRIPT_NAME" "(PBR Generated)|(EASY-INSTALL-(SCRIPT|ENTRY-SCRIPT|DEV-SCRIPT))" \
|
|
||||||
&& "$interpreter" "$(type -P python-argcomplete-check-easy-install-script)" "$SCRIPT_NAME") >/dev/null 2>&1; then
|
|
||||||
local ARGCOMPLETE=1
|
|
||||||
elif __python_argcomplete_run "$interpreter" -m argcomplete._check_console_script "$SCRIPT_NAME"; then
|
|
||||||
local ARGCOMPLETE=1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ $ARGCOMPLETE != 0 ]]; then
|
|
||||||
local IFS=$(echo -e '\v')
|
|
||||||
COMPREPLY=( $(_ARGCOMPLETE_IFS="$IFS" \
|
|
||||||
COMP_LINE="$COMP_LINE" \
|
|
||||||
COMP_POINT="$COMP_POINT" \
|
|
||||||
COMP_TYPE="$COMP_TYPE" \
|
|
||||||
_ARGCOMPLETE_COMP_WORDBREAKS="$COMP_WORDBREAKS" \
|
|
||||||
_ARGCOMPLETE=$ARGCOMPLETE \
|
|
||||||
_ARGCOMPLETE_SUPPRESS_SPACE=1 \
|
|
||||||
__python_argcomplete_run "$executable" "${COMP_WORDS[@]:1:ARGCOMPLETE-1}") )
|
|
||||||
if [[ $? != 0 ]]; then
|
|
||||||
unset COMPREPLY
|
|
||||||
elif [[ "${COMPREPLY-}" =~ [=/:]$ ]]; then
|
|
||||||
compopt -o nospace
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
type -t _completion_loader | grep -q 'function' && _completion_loader "$@"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
complete -o default -o bashdefault -D -F _python_argcomplete_global
|
|
|
@ -1,112 +0,0 @@
|
||||||
# Copyright 2012-2021, Andrey Kislyuk and argcomplete contributors.
|
|
||||||
# Licensed under the Apache License. See https://github.com/kislyuk/argcomplete for more info.
|
|
||||||
|
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
|
|
||||||
def _call(*args, **kwargs):
|
|
||||||
# TODO: replace "universal_newlines" with "text" once 3.6 support is dropped
|
|
||||||
kwargs["universal_newlines"] = True
|
|
||||||
try:
|
|
||||||
return subprocess.check_output(*args, **kwargs).splitlines()
|
|
||||||
except subprocess.CalledProcessError:
|
|
||||||
return []
|
|
||||||
|
|
||||||
|
|
||||||
class ChoicesCompleter(object):
|
|
||||||
def __init__(self, choices):
|
|
||||||
self.choices = choices
|
|
||||||
|
|
||||||
def _convert(self, choice):
|
|
||||||
if not isinstance(choice, str):
|
|
||||||
choice = str(choice)
|
|
||||||
return choice
|
|
||||||
|
|
||||||
def __call__(self, **kwargs):
|
|
||||||
return (self._convert(c) for c in self.choices)
|
|
||||||
|
|
||||||
|
|
||||||
EnvironCompleter = ChoicesCompleter(os.environ)
|
|
||||||
|
|
||||||
|
|
||||||
class FilesCompleter(object):
|
|
||||||
"""
|
|
||||||
File completer class, optionally takes a list of allowed extensions
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, allowednames=(), directories=True):
|
|
||||||
# Fix if someone passes in a string instead of a list
|
|
||||||
if isinstance(allowednames, (str, bytes)):
|
|
||||||
allowednames = [allowednames]
|
|
||||||
|
|
||||||
self.allowednames = [x.lstrip("*").lstrip(".") for x in allowednames]
|
|
||||||
self.directories = directories
|
|
||||||
|
|
||||||
def __call__(self, prefix, **kwargs):
|
|
||||||
completion = []
|
|
||||||
if self.allowednames:
|
|
||||||
if self.directories:
|
|
||||||
files = _call(["bash", "-c", "compgen -A directory -- '{p}'".format(p=prefix)])
|
|
||||||
completion += [f + "/" for f in files]
|
|
||||||
for x in self.allowednames:
|
|
||||||
completion += _call(["bash", "-c", "compgen -A file -X '!*.{0}' -- '{p}'".format(x, p=prefix)])
|
|
||||||
else:
|
|
||||||
completion += _call(["bash", "-c", "compgen -A file -- '{p}'".format(p=prefix)])
|
|
||||||
anticomp = _call(["bash", "-c", "compgen -A directory -- '{p}'".format(p=prefix)])
|
|
||||||
completion = list(set(completion) - set(anticomp))
|
|
||||||
|
|
||||||
if self.directories:
|
|
||||||
completion += [f + "/" for f in anticomp]
|
|
||||||
return completion
|
|
||||||
|
|
||||||
|
|
||||||
class _FilteredFilesCompleter(object):
|
|
||||||
def __init__(self, predicate):
|
|
||||||
"""
|
|
||||||
Create the completer
|
|
||||||
|
|
||||||
A predicate accepts as its only argument a candidate path and either
|
|
||||||
accepts it or rejects it.
|
|
||||||
"""
|
|
||||||
assert predicate, "Expected a callable predicate"
|
|
||||||
self.predicate = predicate
|
|
||||||
|
|
||||||
def __call__(self, prefix, **kwargs):
|
|
||||||
"""
|
|
||||||
Provide completions on prefix
|
|
||||||
"""
|
|
||||||
target_dir = os.path.dirname(prefix)
|
|
||||||
try:
|
|
||||||
names = os.listdir(target_dir or ".")
|
|
||||||
except Exception:
|
|
||||||
return # empty iterator
|
|
||||||
incomplete_part = os.path.basename(prefix)
|
|
||||||
# Iterate on target_dir entries and filter on given predicate
|
|
||||||
for name in names:
|
|
||||||
if not name.startswith(incomplete_part):
|
|
||||||
continue
|
|
||||||
candidate = os.path.join(target_dir, name)
|
|
||||||
if not self.predicate(candidate):
|
|
||||||
continue
|
|
||||||
yield candidate + "/" if os.path.isdir(candidate) else candidate
|
|
||||||
|
|
||||||
|
|
||||||
class DirectoriesCompleter(_FilteredFilesCompleter):
|
|
||||||
def __init__(self):
|
|
||||||
_FilteredFilesCompleter.__init__(self, predicate=os.path.isdir)
|
|
||||||
|
|
||||||
|
|
||||||
class SuppressCompleter(object):
|
|
||||||
"""
|
|
||||||
A completer used to suppress the completion of specific arguments
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def suppress(self):
|
|
||||||
"""
|
|
||||||
Decide if the completion should be suppressed
|
|
||||||
"""
|
|
||||||
return True
|
|
|
@ -1,342 +0,0 @@
|
||||||
# Copyright 2012-2021, Andrey Kislyuk and argcomplete contributors.
|
|
||||||
# Licensed under the Apache License. See https://github.com/kislyuk/argcomplete for more info.
|
|
||||||
|
|
||||||
import typing as t
|
|
||||||
from argparse import _ # type:ignore
|
|
||||||
from argparse import (
|
|
||||||
ONE_OR_MORE,
|
|
||||||
OPTIONAL,
|
|
||||||
PARSER,
|
|
||||||
REMAINDER,
|
|
||||||
SUPPRESS,
|
|
||||||
ZERO_OR_MORE,
|
|
||||||
ArgumentError,
|
|
||||||
ArgumentParser,
|
|
||||||
_get_action_name,
|
|
||||||
_SubParsersAction,
|
|
||||||
)
|
|
||||||
|
|
||||||
_num_consumed_args: t.Dict[str, int] = {}
|
|
||||||
|
|
||||||
|
|
||||||
def action_is_satisfied(action):
|
|
||||||
''' Returns False if the parse would raise an error if no more arguments are given to this action, True otherwise.
|
|
||||||
'''
|
|
||||||
num_consumed_args = _num_consumed_args.get(action, 0)
|
|
||||||
|
|
||||||
if action.nargs in [OPTIONAL, ZERO_OR_MORE, REMAINDER]:
|
|
||||||
return True
|
|
||||||
if action.nargs == ONE_OR_MORE:
|
|
||||||
return num_consumed_args >= 1
|
|
||||||
if action.nargs == PARSER:
|
|
||||||
# Not sure what this should be, but this previously always returned False
|
|
||||||
# so at least this won't break anything that wasn't already broken.
|
|
||||||
return False
|
|
||||||
if action.nargs is None:
|
|
||||||
return num_consumed_args == 1
|
|
||||||
|
|
||||||
assert isinstance(action.nargs, int), 'failed to handle a possible nargs value: %r' % action.nargs
|
|
||||||
return num_consumed_args == action.nargs
|
|
||||||
|
|
||||||
|
|
||||||
def action_is_open(action):
|
|
||||||
''' Returns True if action could consume more arguments (i.e., its pattern is open).
|
|
||||||
'''
|
|
||||||
num_consumed_args = _num_consumed_args.get(action, 0)
|
|
||||||
|
|
||||||
if action.nargs in [ZERO_OR_MORE, ONE_OR_MORE, PARSER, REMAINDER]:
|
|
||||||
return True
|
|
||||||
if action.nargs == OPTIONAL or action.nargs is None:
|
|
||||||
return num_consumed_args == 0
|
|
||||||
|
|
||||||
assert isinstance(action.nargs, int), 'failed to handle a possible nargs value: %r' % action.nargs
|
|
||||||
return num_consumed_args < action.nargs
|
|
||||||
|
|
||||||
|
|
||||||
def action_is_greedy(action, isoptional=False):
|
|
||||||
''' Returns True if action will necessarily consume the next argument.
|
|
||||||
isoptional indicates whether the argument is an optional (starts with -).
|
|
||||||
'''
|
|
||||||
num_consumed_args = _num_consumed_args.get(action, 0)
|
|
||||||
|
|
||||||
if action.option_strings:
|
|
||||||
if not isoptional and not action_is_satisfied(action):
|
|
||||||
return True
|
|
||||||
return action.nargs == REMAINDER
|
|
||||||
else:
|
|
||||||
return action.nargs == REMAINDER and num_consumed_args >= 1
|
|
||||||
|
|
||||||
|
|
||||||
class IntrospectiveArgumentParser(ArgumentParser):
|
|
||||||
''' The following is a verbatim copy of ArgumentParser._parse_known_args (Python 2.7.3),
|
|
||||||
except for the lines that contain the string "Added by argcomplete".
|
|
||||||
'''
|
|
||||||
|
|
||||||
def _parse_known_args(self, arg_strings, namespace):
|
|
||||||
_num_consumed_args.clear() # Added by argcomplete
|
|
||||||
self._argcomplete_namespace = namespace
|
|
||||||
self.active_actions = [] # Added by argcomplete
|
|
||||||
# replace arg strings that are file references
|
|
||||||
if self.fromfile_prefix_chars is not None:
|
|
||||||
arg_strings = self._read_args_from_files(arg_strings)
|
|
||||||
|
|
||||||
# map all mutually exclusive arguments to the other arguments
|
|
||||||
# they can't occur with
|
|
||||||
action_conflicts = {}
|
|
||||||
self._action_conflicts = action_conflicts # Added by argcomplete
|
|
||||||
for mutex_group in self._mutually_exclusive_groups:
|
|
||||||
group_actions = mutex_group._group_actions
|
|
||||||
for i, mutex_action in enumerate(mutex_group._group_actions):
|
|
||||||
conflicts = action_conflicts.setdefault(mutex_action, [])
|
|
||||||
conflicts.extend(group_actions[:i])
|
|
||||||
conflicts.extend(group_actions[i + 1:])
|
|
||||||
|
|
||||||
# find all option indices, and determine the arg_string_pattern
|
|
||||||
# which has an 'O' if there is an option at an index,
|
|
||||||
# an 'A' if there is an argument, or a '-' if there is a '--'
|
|
||||||
option_string_indices = {}
|
|
||||||
arg_string_pattern_parts = []
|
|
||||||
arg_strings_iter = iter(arg_strings)
|
|
||||||
for i, arg_string in enumerate(arg_strings_iter):
|
|
||||||
|
|
||||||
# all args after -- are non-options
|
|
||||||
if arg_string == '--':
|
|
||||||
arg_string_pattern_parts.append('-')
|
|
||||||
for arg_string in arg_strings_iter:
|
|
||||||
arg_string_pattern_parts.append('A')
|
|
||||||
|
|
||||||
# otherwise, add the arg to the arg strings
|
|
||||||
# and note the index if it was an option
|
|
||||||
else:
|
|
||||||
option_tuple = self._parse_optional(arg_string)
|
|
||||||
if option_tuple is None:
|
|
||||||
pattern = 'A'
|
|
||||||
else:
|
|
||||||
option_string_indices[i] = option_tuple
|
|
||||||
pattern = 'O'
|
|
||||||
arg_string_pattern_parts.append(pattern)
|
|
||||||
|
|
||||||
# join the pieces together to form the pattern
|
|
||||||
arg_strings_pattern = ''.join(arg_string_pattern_parts)
|
|
||||||
|
|
||||||
# converts arg strings to the appropriate and then takes the action
|
|
||||||
seen_actions = set()
|
|
||||||
seen_non_default_actions = set()
|
|
||||||
self._seen_non_default_actions = seen_non_default_actions # Added by argcomplete
|
|
||||||
|
|
||||||
def take_action(action, argument_strings, option_string=None):
|
|
||||||
seen_actions.add(action)
|
|
||||||
argument_values = self._get_values(action, argument_strings)
|
|
||||||
|
|
||||||
# error if this argument is not allowed with other previously
|
|
||||||
# seen arguments, assuming that actions that use the default
|
|
||||||
# value don't really count as "present"
|
|
||||||
if argument_values is not action.default:
|
|
||||||
seen_non_default_actions.add(action)
|
|
||||||
for conflict_action in action_conflicts.get(action, []):
|
|
||||||
if conflict_action in seen_non_default_actions:
|
|
||||||
msg = _('not allowed with argument %s')
|
|
||||||
action_name = _get_action_name(conflict_action)
|
|
||||||
raise ArgumentError(action, msg % action_name)
|
|
||||||
|
|
||||||
# take the action if we didn't receive a SUPPRESS value
|
|
||||||
# (e.g. from a default)
|
|
||||||
if argument_values is not SUPPRESS \
|
|
||||||
or isinstance(action, _SubParsersAction):
|
|
||||||
try:
|
|
||||||
action(self, namespace, argument_values, option_string)
|
|
||||||
except BaseException:
|
|
||||||
# Begin added by argcomplete
|
|
||||||
# When a subparser action is taken and fails due to incomplete arguments, it does not merge the
|
|
||||||
# contents of its parsed namespace into the parent namespace. Do that here to allow completers to
|
|
||||||
# access the partially parsed arguments for the subparser.
|
|
||||||
if isinstance(action, _SubParsersAction):
|
|
||||||
subnamespace = action._name_parser_map[argument_values[0]]._argcomplete_namespace
|
|
||||||
for key, value in vars(subnamespace).items():
|
|
||||||
setattr(namespace, key, value)
|
|
||||||
# End added by argcomplete
|
|
||||||
raise
|
|
||||||
|
|
||||||
# function to convert arg_strings into an optional action
|
|
||||||
def consume_optional(start_index):
|
|
||||||
|
|
||||||
# get the optional identified at this index
|
|
||||||
option_tuple = option_string_indices[start_index]
|
|
||||||
action, option_string, explicit_arg = option_tuple
|
|
||||||
|
|
||||||
# identify additional optionals in the same arg string
|
|
||||||
# (e.g. -xyz is the same as -x -y -z if no args are required)
|
|
||||||
match_argument = self._match_argument
|
|
||||||
action_tuples = []
|
|
||||||
while True:
|
|
||||||
|
|
||||||
# if we found no optional action, skip it
|
|
||||||
if action is None:
|
|
||||||
extras.append(arg_strings[start_index])
|
|
||||||
return start_index + 1
|
|
||||||
|
|
||||||
# if there is an explicit argument, try to match the
|
|
||||||
# optional's string arguments to only this
|
|
||||||
if explicit_arg is not None:
|
|
||||||
arg_count = match_argument(action, 'A')
|
|
||||||
|
|
||||||
# if the action is a single-dash option and takes no
|
|
||||||
# arguments, try to parse more single-dash options out
|
|
||||||
# of the tail of the option string
|
|
||||||
chars = self.prefix_chars
|
|
||||||
if arg_count == 0 and option_string[1] not in chars:
|
|
||||||
action_tuples.append((action, [], option_string))
|
|
||||||
char = option_string[0]
|
|
||||||
option_string = char + explicit_arg[0]
|
|
||||||
new_explicit_arg = explicit_arg[1:] or None
|
|
||||||
optionals_map = self._option_string_actions
|
|
||||||
if option_string in optionals_map:
|
|
||||||
action = optionals_map[option_string]
|
|
||||||
explicit_arg = new_explicit_arg
|
|
||||||
else:
|
|
||||||
msg = _('ignored explicit argument %r')
|
|
||||||
raise ArgumentError(action, msg % explicit_arg)
|
|
||||||
|
|
||||||
# if the action expect exactly one argument, we've
|
|
||||||
# successfully matched the option; exit the loop
|
|
||||||
elif arg_count == 1:
|
|
||||||
stop = start_index + 1
|
|
||||||
args = [explicit_arg]
|
|
||||||
action_tuples.append((action, args, option_string))
|
|
||||||
break
|
|
||||||
|
|
||||||
# error if a double-dash option did not use the
|
|
||||||
# explicit argument
|
|
||||||
else:
|
|
||||||
msg = _('ignored explicit argument %r')
|
|
||||||
raise ArgumentError(action, msg % explicit_arg)
|
|
||||||
|
|
||||||
# if there is no explicit argument, try to match the
|
|
||||||
# optional's string arguments with the following strings
|
|
||||||
# if successful, exit the loop
|
|
||||||
else:
|
|
||||||
start = start_index + 1
|
|
||||||
selected_patterns = arg_strings_pattern[start:]
|
|
||||||
self.active_actions = [action] # Added by argcomplete
|
|
||||||
_num_consumed_args[action] = 0 # Added by argcomplete
|
|
||||||
arg_count = match_argument(action, selected_patterns)
|
|
||||||
stop = start + arg_count
|
|
||||||
args = arg_strings[start:stop]
|
|
||||||
|
|
||||||
# Begin added by argcomplete
|
|
||||||
# If the pattern is not open (e.g. no + at the end), remove the action from active actions (since
|
|
||||||
# it wouldn't be able to consume any more args)
|
|
||||||
_num_consumed_args[action] = len(args)
|
|
||||||
if not action_is_open(action):
|
|
||||||
self.active_actions.remove(action)
|
|
||||||
# End added by argcomplete
|
|
||||||
|
|
||||||
action_tuples.append((action, args, option_string))
|
|
||||||
break
|
|
||||||
|
|
||||||
# add the Optional to the list and return the index at which
|
|
||||||
# the Optional's string args stopped
|
|
||||||
assert action_tuples
|
|
||||||
for action, args, option_string in action_tuples:
|
|
||||||
take_action(action, args, option_string)
|
|
||||||
return stop
|
|
||||||
|
|
||||||
# the list of Positionals left to be parsed; this is modified
|
|
||||||
# by consume_positionals()
|
|
||||||
positionals = self._get_positional_actions()
|
|
||||||
|
|
||||||
# function to convert arg_strings into positional actions
|
|
||||||
def consume_positionals(start_index):
|
|
||||||
# match as many Positionals as possible
|
|
||||||
match_partial = self._match_arguments_partial
|
|
||||||
selected_pattern = arg_strings_pattern[start_index:]
|
|
||||||
arg_counts = match_partial(positionals, selected_pattern)
|
|
||||||
|
|
||||||
# slice off the appropriate arg strings for each Positional
|
|
||||||
# and add the Positional and its args to the list
|
|
||||||
for action, arg_count in zip(positionals, arg_counts): # Added by argcomplete
|
|
||||||
self.active_actions.append(action) # Added by argcomplete
|
|
||||||
for action, arg_count in zip(positionals, arg_counts):
|
|
||||||
args = arg_strings[start_index: start_index + arg_count]
|
|
||||||
start_index += arg_count
|
|
||||||
_num_consumed_args[action] = len(args) # Added by argcomplete
|
|
||||||
take_action(action, args)
|
|
||||||
|
|
||||||
# slice off the Positionals that we just parsed and return the
|
|
||||||
# index at which the Positionals' string args stopped
|
|
||||||
positionals[:] = positionals[len(arg_counts):]
|
|
||||||
return start_index
|
|
||||||
|
|
||||||
# consume Positionals and Optionals alternately, until we have
|
|
||||||
# passed the last option string
|
|
||||||
extras = []
|
|
||||||
start_index = 0
|
|
||||||
if option_string_indices:
|
|
||||||
max_option_string_index = max(option_string_indices)
|
|
||||||
else:
|
|
||||||
max_option_string_index = -1
|
|
||||||
while start_index <= max_option_string_index:
|
|
||||||
|
|
||||||
# consume any Positionals preceding the next option
|
|
||||||
next_option_string_index = min([
|
|
||||||
index
|
|
||||||
for index in option_string_indices
|
|
||||||
if index >= start_index])
|
|
||||||
if start_index != next_option_string_index:
|
|
||||||
positionals_end_index = consume_positionals(start_index)
|
|
||||||
|
|
||||||
# only try to parse the next optional if we didn't consume
|
|
||||||
# the option string during the positionals parsing
|
|
||||||
if positionals_end_index > start_index:
|
|
||||||
start_index = positionals_end_index
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
start_index = positionals_end_index
|
|
||||||
|
|
||||||
# if we consumed all the positionals we could and we're not
|
|
||||||
# at the index of an option string, there were extra arguments
|
|
||||||
if start_index not in option_string_indices:
|
|
||||||
strings = arg_strings[start_index:next_option_string_index]
|
|
||||||
extras.extend(strings)
|
|
||||||
start_index = next_option_string_index
|
|
||||||
|
|
||||||
# consume the next optional and any arguments for it
|
|
||||||
start_index = consume_optional(start_index)
|
|
||||||
|
|
||||||
# consume any positionals following the last Optional
|
|
||||||
stop_index = consume_positionals(start_index)
|
|
||||||
|
|
||||||
# if we didn't consume all the argument strings, there were extras
|
|
||||||
extras.extend(arg_strings[stop_index:])
|
|
||||||
|
|
||||||
# if we didn't use all the Positional objects, there were too few
|
|
||||||
# arg strings supplied.
|
|
||||||
|
|
||||||
if positionals:
|
|
||||||
self.active_actions.append(positionals[0]) # Added by argcomplete
|
|
||||||
self.error(_('too few arguments'))
|
|
||||||
|
|
||||||
# make sure all required actions were present
|
|
||||||
for action in self._actions:
|
|
||||||
if action.required:
|
|
||||||
if action not in seen_actions:
|
|
||||||
name = _get_action_name(action)
|
|
||||||
self.error(_('argument %s is required') % name)
|
|
||||||
|
|
||||||
# make sure all required groups had one option present
|
|
||||||
for group in self._mutually_exclusive_groups:
|
|
||||||
if group.required:
|
|
||||||
for action in group._group_actions:
|
|
||||||
if action in seen_non_default_actions:
|
|
||||||
break
|
|
||||||
|
|
||||||
# if no actions were used, report the error
|
|
||||||
else:
|
|
||||||
names = [_get_action_name(action)
|
|
||||||
for action in group._group_actions
|
|
||||||
if action.help is not SUPPRESS]
|
|
||||||
msg = _('one of the arguments %s is required')
|
|
||||||
self.error(msg % ' '.join(names))
|
|
||||||
|
|
||||||
# return the updated namespace and the extra arguments
|
|
||||||
return namespace, extras
|
|
|
@ -1,328 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# This copy of shlex.py from Python 3.6 is distributed with argcomplete.
|
|
||||||
# It contains only the shlex class, with modifications as noted.
|
|
||||||
|
|
||||||
"""A lexical analyzer class for simple shell-like syntaxes."""
|
|
||||||
|
|
||||||
# Module and documentation by Eric S. Raymond, 21 Dec 1998
|
|
||||||
# Input stacking and error message cleanup added by ESR, March 2000
|
|
||||||
# push_source() and pop_source() made explicit by ESR, January 2001.
|
|
||||||
# Posix compliance, split(), string arguments, and
|
|
||||||
# iterator interface by Gustavo Niemeyer, April 2003.
|
|
||||||
# changes to tokenize more like Posix shells by Vinay Sajip, July 2016.
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
from collections import deque
|
|
||||||
|
|
||||||
# Modified by argcomplete: 2/3 compatibility
|
|
||||||
# Note: cStringIO is not compatible with Unicode
|
|
||||||
try:
|
|
||||||
from StringIO import StringIO # type:ignore
|
|
||||||
except ImportError:
|
|
||||||
from io import StringIO
|
|
||||||
|
|
||||||
# Modified by argcomplete: 2/3 compatibility
|
|
||||||
try:
|
|
||||||
basestring # type:ignore
|
|
||||||
except NameError:
|
|
||||||
basestring = str
|
|
||||||
|
|
||||||
class shlex:
|
|
||||||
"A lexical analyzer class for simple shell-like syntaxes."
|
|
||||||
def __init__(self, instream=None, infile=None, posix=False,
|
|
||||||
punctuation_chars=False):
|
|
||||||
# Modified by argcomplete: 2/3 compatibility
|
|
||||||
if isinstance(instream, basestring):
|
|
||||||
instream = StringIO(instream)
|
|
||||||
if instream is not None:
|
|
||||||
self.instream = instream
|
|
||||||
self.infile = infile
|
|
||||||
else:
|
|
||||||
self.instream = sys.stdin
|
|
||||||
self.infile = None
|
|
||||||
self.posix = posix
|
|
||||||
if posix:
|
|
||||||
self.eof = None
|
|
||||||
else:
|
|
||||||
self.eof = ''
|
|
||||||
self.commenters = '#'
|
|
||||||
self.wordchars = ('abcdfeghijklmnopqrstuvwxyz'
|
|
||||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_')
|
|
||||||
# Modified by argcomplete: 2/3 compatibility
|
|
||||||
# if self.posix:
|
|
||||||
# self.wordchars += ('ßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ'
|
|
||||||
# 'ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞ')
|
|
||||||
self.whitespace = ' \t\r\n'
|
|
||||||
self.whitespace_split = False
|
|
||||||
self.quotes = '\'"'
|
|
||||||
self.escape = '\\'
|
|
||||||
self.escapedquotes = '"'
|
|
||||||
self.state = ' '
|
|
||||||
self.pushback = deque()
|
|
||||||
self.lineno = 1
|
|
||||||
self.debug = 0
|
|
||||||
self.token = ''
|
|
||||||
self.filestack = deque()
|
|
||||||
self.source = None
|
|
||||||
if not punctuation_chars:
|
|
||||||
punctuation_chars = ''
|
|
||||||
elif punctuation_chars is True:
|
|
||||||
punctuation_chars = '();<>|&'
|
|
||||||
self.punctuation_chars = punctuation_chars
|
|
||||||
if punctuation_chars:
|
|
||||||
# _pushback_chars is a push back queue used by lookahead logic
|
|
||||||
self._pushback_chars = deque()
|
|
||||||
# these chars added because allowed in file names, args, wildcards
|
|
||||||
self.wordchars += '~-./*?='
|
|
||||||
#remove any punctuation chars from wordchars
|
|
||||||
t = self.wordchars.maketrans(dict.fromkeys(punctuation_chars))
|
|
||||||
self.wordchars = self.wordchars.translate(t)
|
|
||||||
|
|
||||||
# Modified by argcomplete: Record last wordbreak position
|
|
||||||
self.last_wordbreak_pos = None
|
|
||||||
self.wordbreaks = ''
|
|
||||||
|
|
||||||
def push_token(self, tok):
|
|
||||||
"Push a token onto the stack popped by the get_token method"
|
|
||||||
if self.debug >= 1:
|
|
||||||
print("shlex: pushing token " + repr(tok))
|
|
||||||
self.pushback.appendleft(tok)
|
|
||||||
|
|
||||||
def push_source(self, newstream, newfile=None):
|
|
||||||
"Push an input source onto the lexer's input source stack."
|
|
||||||
# Modified by argcomplete: 2/3 compatibility
|
|
||||||
if isinstance(newstream, basestring):
|
|
||||||
newstream = StringIO(newstream)
|
|
||||||
self.filestack.appendleft((self.infile, self.instream, self.lineno))
|
|
||||||
self.infile = newfile
|
|
||||||
self.instream = newstream
|
|
||||||
self.lineno = 1
|
|
||||||
if self.debug:
|
|
||||||
if newfile is not None:
|
|
||||||
print('shlex: pushing to file %s' % (self.infile,))
|
|
||||||
else:
|
|
||||||
print('shlex: pushing to stream %s' % (self.instream,))
|
|
||||||
|
|
||||||
def pop_source(self):
|
|
||||||
"Pop the input source stack."
|
|
||||||
self.instream.close()
|
|
||||||
(self.infile, self.instream, self.lineno) = self.filestack.popleft()
|
|
||||||
if self.debug:
|
|
||||||
print('shlex: popping to %s, line %d' \
|
|
||||||
% (self.instream, self.lineno))
|
|
||||||
self.state = ' '
|
|
||||||
|
|
||||||
def get_token(self):
|
|
||||||
"Get a token from the input stream (or from stack if it's nonempty)"
|
|
||||||
if self.pushback:
|
|
||||||
tok = self.pushback.popleft()
|
|
||||||
if self.debug >= 1:
|
|
||||||
print("shlex: popping token " + repr(tok))
|
|
||||||
return tok
|
|
||||||
# No pushback. Get a token.
|
|
||||||
raw = self.read_token()
|
|
||||||
# Handle inclusions
|
|
||||||
if self.source is not None:
|
|
||||||
while raw == self.source:
|
|
||||||
spec = self.sourcehook(self.read_token())
|
|
||||||
if spec:
|
|
||||||
(newfile, newstream) = spec
|
|
||||||
self.push_source(newstream, newfile)
|
|
||||||
raw = self.get_token()
|
|
||||||
# Maybe we got EOF instead?
|
|
||||||
while raw == self.eof:
|
|
||||||
if not self.filestack:
|
|
||||||
return self.eof
|
|
||||||
else:
|
|
||||||
self.pop_source()
|
|
||||||
raw = self.get_token()
|
|
||||||
# Neither inclusion nor EOF
|
|
||||||
if self.debug >= 1:
|
|
||||||
if raw != self.eof:
|
|
||||||
print("shlex: token=" + repr(raw))
|
|
||||||
else:
|
|
||||||
print("shlex: token=EOF")
|
|
||||||
return raw
|
|
||||||
|
|
||||||
def read_token(self):
|
|
||||||
quoted = False
|
|
||||||
escapedstate = ' '
|
|
||||||
while True:
|
|
||||||
if self.punctuation_chars and self._pushback_chars:
|
|
||||||
nextchar = self._pushback_chars.pop()
|
|
||||||
else:
|
|
||||||
nextchar = self.instream.read(1)
|
|
||||||
if nextchar == '\n':
|
|
||||||
self.lineno += 1
|
|
||||||
if self.debug >= 3:
|
|
||||||
print("shlex: in state %r I see character: %r" % (self.state,
|
|
||||||
nextchar))
|
|
||||||
if self.state is None:
|
|
||||||
self.token = '' # past end of file
|
|
||||||
break
|
|
||||||
elif self.state == ' ':
|
|
||||||
if not nextchar:
|
|
||||||
self.state = None # end of file
|
|
||||||
break
|
|
||||||
elif nextchar in self.whitespace:
|
|
||||||
if self.debug >= 2:
|
|
||||||
print("shlex: I see whitespace in whitespace state")
|
|
||||||
if self.token or (self.posix and quoted):
|
|
||||||
break # emit current token
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
elif nextchar in self.commenters:
|
|
||||||
self.instream.readline()
|
|
||||||
self.lineno += 1
|
|
||||||
elif self.posix and nextchar in self.escape:
|
|
||||||
escapedstate = 'a'
|
|
||||||
self.state = nextchar
|
|
||||||
elif nextchar in self.wordchars:
|
|
||||||
self.token = nextchar
|
|
||||||
self.state = 'a'
|
|
||||||
elif nextchar in self.punctuation_chars:
|
|
||||||
self.token = nextchar
|
|
||||||
self.state = 'c'
|
|
||||||
elif nextchar in self.quotes:
|
|
||||||
if not self.posix:
|
|
||||||
self.token = nextchar
|
|
||||||
self.state = nextchar
|
|
||||||
elif self.whitespace_split:
|
|
||||||
self.token = nextchar
|
|
||||||
self.state = 'a'
|
|
||||||
else:
|
|
||||||
self.token = nextchar
|
|
||||||
if self.token or (self.posix and quoted):
|
|
||||||
break # emit current token
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
elif self.state in self.quotes:
|
|
||||||
quoted = True
|
|
||||||
if not nextchar: # end of file
|
|
||||||
if self.debug >= 2:
|
|
||||||
print("shlex: I see EOF in quotes state")
|
|
||||||
# XXX what error should be raised here?
|
|
||||||
raise ValueError("No closing quotation")
|
|
||||||
if nextchar == self.state:
|
|
||||||
if not self.posix:
|
|
||||||
self.token += nextchar
|
|
||||||
self.state = ' '
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
self.state = 'a'
|
|
||||||
elif (self.posix and nextchar in self.escape and self.state
|
|
||||||
in self.escapedquotes):
|
|
||||||
escapedstate = self.state
|
|
||||||
self.state = nextchar
|
|
||||||
else:
|
|
||||||
self.token += nextchar
|
|
||||||
elif self.state in self.escape:
|
|
||||||
if not nextchar: # end of file
|
|
||||||
if self.debug >= 2:
|
|
||||||
print("shlex: I see EOF in escape state")
|
|
||||||
# XXX what error should be raised here?
|
|
||||||
raise ValueError("No escaped character")
|
|
||||||
# In posix shells, only the quote itself or the escape
|
|
||||||
# character may be escaped within quotes.
|
|
||||||
if (escapedstate in self.quotes and
|
|
||||||
nextchar != self.state and nextchar != escapedstate):
|
|
||||||
self.token += self.state
|
|
||||||
self.token += nextchar
|
|
||||||
self.state = escapedstate
|
|
||||||
elif self.state in ('a', 'c'):
|
|
||||||
if not nextchar:
|
|
||||||
self.state = None # end of file
|
|
||||||
break
|
|
||||||
elif nextchar in self.whitespace:
|
|
||||||
if self.debug >= 2:
|
|
||||||
print("shlex: I see whitespace in word state")
|
|
||||||
self.state = ' '
|
|
||||||
if self.token or (self.posix and quoted):
|
|
||||||
break # emit current token
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
elif nextchar in self.commenters:
|
|
||||||
self.instream.readline()
|
|
||||||
self.lineno += 1
|
|
||||||
if self.posix:
|
|
||||||
self.state = ' '
|
|
||||||
if self.token or (self.posix and quoted):
|
|
||||||
break # emit current token
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
elif self.posix and nextchar in self.quotes:
|
|
||||||
self.state = nextchar
|
|
||||||
elif self.posix and nextchar in self.escape:
|
|
||||||
escapedstate = 'a'
|
|
||||||
self.state = nextchar
|
|
||||||
elif self.state == 'c':
|
|
||||||
if nextchar in self.punctuation_chars:
|
|
||||||
self.token += nextchar
|
|
||||||
else:
|
|
||||||
if nextchar not in self.whitespace:
|
|
||||||
self._pushback_chars.append(nextchar)
|
|
||||||
self.state = ' '
|
|
||||||
break
|
|
||||||
elif (nextchar in self.wordchars or nextchar in self.quotes
|
|
||||||
or self.whitespace_split):
|
|
||||||
self.token += nextchar
|
|
||||||
# Modified by argcomplete: Record last wordbreak position
|
|
||||||
if nextchar in self.wordbreaks:
|
|
||||||
self.last_wordbreak_pos = len(self.token) - 1
|
|
||||||
else:
|
|
||||||
if self.punctuation_chars:
|
|
||||||
self._pushback_chars.append(nextchar)
|
|
||||||
else:
|
|
||||||
self.pushback.appendleft(nextchar)
|
|
||||||
if self.debug >= 2:
|
|
||||||
print("shlex: I see punctuation in word state")
|
|
||||||
self.state = ' '
|
|
||||||
if self.token or (self.posix and quoted):
|
|
||||||
break # emit current token
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
result = self.token
|
|
||||||
self.token = ''
|
|
||||||
if self.posix and not quoted and result == '':
|
|
||||||
result = None
|
|
||||||
if self.debug > 1:
|
|
||||||
if result:
|
|
||||||
print("shlex: raw token=" + repr(result))
|
|
||||||
else:
|
|
||||||
print("shlex: raw token=EOF")
|
|
||||||
# Modified by argcomplete: Record last wordbreak position
|
|
||||||
if self.state == ' ':
|
|
||||||
self.last_wordbreak_pos = None
|
|
||||||
return result
|
|
||||||
|
|
||||||
def sourcehook(self, newfile):
|
|
||||||
"Hook called on a filename to be sourced."
|
|
||||||
if newfile[0] == '"':
|
|
||||||
newfile = newfile[1:-1]
|
|
||||||
# This implements cpp-like semantics for relative-path inclusion.
|
|
||||||
# Modified by argcomplete: 2/3 compatibility
|
|
||||||
if isinstance(self.infile, basestring) and not os.path.isabs(newfile):
|
|
||||||
newfile = os.path.join(os.path.dirname(self.infile), newfile)
|
|
||||||
return (newfile, open(newfile, "r"))
|
|
||||||
|
|
||||||
def error_leader(self, infile=None, lineno=None):
|
|
||||||
"Emit a C-compiler-like, Emacs-friendly error-message leader."
|
|
||||||
if infile is None:
|
|
||||||
infile = self.infile
|
|
||||||
if lineno is None:
|
|
||||||
lineno = self.lineno
|
|
||||||
return "\"%s\", line %d: " % (infile, lineno)
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __next__(self):
|
|
||||||
token = self.get_token()
|
|
||||||
if token == self.eof:
|
|
||||||
raise StopIteration
|
|
||||||
return token
|
|
||||||
|
|
||||||
# Modified by argcomplete: 2/3 compatibility
|
|
||||||
next = __next__
|
|
|
@ -1,165 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
try:
|
|
||||||
from shlex import quote
|
|
||||||
except ImportError:
|
|
||||||
from pipes import quote
|
|
||||||
|
|
||||||
bashcode = r"""
|
|
||||||
# Run something, muting output or redirecting it to the debug stream
|
|
||||||
# depending on the value of _ARC_DEBUG.
|
|
||||||
# If ARGCOMPLETE_USE_TEMPFILES is set, use tempfiles for IPC.
|
|
||||||
__python_argcomplete_run() {
|
|
||||||
if [[ -z "${ARGCOMPLETE_USE_TEMPFILES-}" ]]; then
|
|
||||||
__python_argcomplete_run_inner "$@"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
local tmpfile="$(mktemp)"
|
|
||||||
_ARGCOMPLETE_STDOUT_FILENAME="$tmpfile" __python_argcomplete_run_inner "$@"
|
|
||||||
local code=$?
|
|
||||||
cat "$tmpfile"
|
|
||||||
rm "$tmpfile"
|
|
||||||
return $code
|
|
||||||
}
|
|
||||||
|
|
||||||
__python_argcomplete_run_inner() {
|
|
||||||
if [[ -z "${_ARC_DEBUG-}" ]]; then
|
|
||||||
"$@" 8>&1 9>&2 1>/dev/null 2>&1
|
|
||||||
else
|
|
||||||
"$@" 8>&1 9>&2 1>&9 2>&1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
_python_argcomplete%(function_suffix)s() {
|
|
||||||
local IFS=$'\013'
|
|
||||||
local SUPPRESS_SPACE=0
|
|
||||||
if compopt +o nospace 2> /dev/null; then
|
|
||||||
SUPPRESS_SPACE=1
|
|
||||||
fi
|
|
||||||
COMPREPLY=( $(IFS="$IFS" \
|
|
||||||
COMP_LINE="$COMP_LINE" \
|
|
||||||
COMP_POINT="$COMP_POINT" \
|
|
||||||
COMP_TYPE="$COMP_TYPE" \
|
|
||||||
_ARGCOMPLETE_COMP_WORDBREAKS="$COMP_WORDBREAKS" \
|
|
||||||
_ARGCOMPLETE=1 \
|
|
||||||
_ARGCOMPLETE_SUPPRESS_SPACE=$SUPPRESS_SPACE \
|
|
||||||
__python_argcomplete_run "%(argcomplete_script)s") )
|
|
||||||
if [[ $? != 0 ]]; then
|
|
||||||
unset COMPREPLY
|
|
||||||
elif [[ $SUPPRESS_SPACE == 1 ]] && [[ "${COMPREPLY-}" =~ [=/:]$ ]]; then
|
|
||||||
compopt -o nospace
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
complete %(complete_opts)s -F _python_argcomplete%(function_suffix)s %(executables)s
|
|
||||||
"""
|
|
||||||
|
|
||||||
tcshcode = """\
|
|
||||||
complete "%(executable)s" 'p@*@`python-argcomplete-tcsh "%(argcomplete_script)s"`@' ;
|
|
||||||
"""
|
|
||||||
|
|
||||||
fishcode = r"""
|
|
||||||
function __fish_%(function_name)s_complete
|
|
||||||
set -x _ARGCOMPLETE 1
|
|
||||||
set -x _ARGCOMPLETE_DFS \t
|
|
||||||
set -x _ARGCOMPLETE_IFS \n
|
|
||||||
set -x _ARGCOMPLETE_SUPPRESS_SPACE 1
|
|
||||||
set -x _ARGCOMPLETE_SHELL fish
|
|
||||||
set -x COMP_LINE (commandline -p)
|
|
||||||
set -x COMP_POINT (string length (commandline -cp))
|
|
||||||
set -x COMP_TYPE
|
|
||||||
if set -q _ARC_DEBUG
|
|
||||||
%(argcomplete_script)s 8>&1 9>&2 1>&9 2>&1
|
|
||||||
else
|
|
||||||
%(argcomplete_script)s 8>&1 9>&2 1>/dev/null 2>&1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
complete %(completion_arg)s %(executable)s -f -a '(__fish_%(function_name)s_complete)'
|
|
||||||
"""
|
|
||||||
|
|
||||||
powershell_code = r"""
|
|
||||||
Register-ArgumentCompleter -Native -CommandName %(executable)s -ScriptBlock {
|
|
||||||
param($commandName, $wordToComplete, $cursorPosition)
|
|
||||||
$completion_file = New-TemporaryFile
|
|
||||||
$env:ARGCOMPLETE_USE_TEMPFILES = 1
|
|
||||||
$env:_ARGCOMPLETE_STDOUT_FILENAME = $completion_file
|
|
||||||
$env:COMP_LINE = $wordToComplete
|
|
||||||
$env:COMP_POINT = $cursorPosition
|
|
||||||
$env:_ARGCOMPLETE = 1
|
|
||||||
$env:_ARGCOMPLETE_SUPPRESS_SPACE = 0
|
|
||||||
$env:_ARGCOMPLETE_IFS = "`n"
|
|
||||||
%(argcomplete_script)s 2>&1 | Out-Null
|
|
||||||
|
|
||||||
Get-Content $completion_file | ForEach-Object {
|
|
||||||
[System.Management.Automation.CompletionResult]::new($_, $_, "ParameterValue", $_)
|
|
||||||
}
|
|
||||||
rm $completion_file
|
|
||||||
Remove-Item Env:\_ARGCOMPLETE_STDOUT_FILENAME, Env:\ARGCOMPLETE_USE_TEMPFILES, Env:\COMP_LINE, Env:\COMP_POINT, Env:\_ARGCOMPLETE, Env:\_ARGCOMPLETE_SUPPRESS_SPACE, Env:\_ARGCOMPLETE_IFS
|
|
||||||
}
|
|
||||||
""" # noqa: E501
|
|
||||||
|
|
||||||
shell_codes = {"bash": bashcode, "tcsh": tcshcode, "fish": fishcode, "powershell": powershell_code}
|
|
||||||
|
|
||||||
|
|
||||||
def shellcode(executables, use_defaults=True, shell="bash", complete_arguments=None, argcomplete_script=None):
|
|
||||||
"""
|
|
||||||
Provide the shell code required to register a python executable for use with the argcomplete module.
|
|
||||||
|
|
||||||
:param list(str) executables: Executables to be completed (when invoked exactly with this name)
|
|
||||||
:param bool use_defaults: Whether to fallback to readline's default completion when no matches are generated.
|
|
||||||
:param str shell: Name of the shell to output code for (bash or tcsh)
|
|
||||||
:param complete_arguments: Arguments to call complete with
|
|
||||||
:type complete_arguments: list(str) or None
|
|
||||||
:param argcomplete_script: Script to call complete with, if not the executable to complete.
|
|
||||||
If supplied, will be used to complete *all* passed executables.
|
|
||||||
:type argcomplete_script: str or None
|
|
||||||
"""
|
|
||||||
|
|
||||||
if complete_arguments is None:
|
|
||||||
complete_options = "-o nospace -o default -o bashdefault" if use_defaults else "-o nospace -o bashdefault"
|
|
||||||
else:
|
|
||||||
complete_options = " ".join(complete_arguments)
|
|
||||||
|
|
||||||
if shell == "bash":
|
|
||||||
quoted_executables = [quote(i) for i in executables]
|
|
||||||
executables_list = " ".join(quoted_executables)
|
|
||||||
script = argcomplete_script
|
|
||||||
if script:
|
|
||||||
function_suffix = "_" + script
|
|
||||||
else:
|
|
||||||
script = "$1"
|
|
||||||
function_suffix = ""
|
|
||||||
code = bashcode % dict(
|
|
||||||
complete_opts=complete_options,
|
|
||||||
executables=executables_list,
|
|
||||||
argcomplete_script=script,
|
|
||||||
function_suffix=function_suffix,
|
|
||||||
)
|
|
||||||
elif shell == "fish":
|
|
||||||
code = ""
|
|
||||||
for executable in executables:
|
|
||||||
script = argcomplete_script or executable
|
|
||||||
completion_arg = "--path" if "/" in executable else "--command" # use path for absolute paths
|
|
||||||
function_name = executable.replace("/", "_") # / not allowed in function name
|
|
||||||
|
|
||||||
code += fishcode % dict(
|
|
||||||
executable=executable,
|
|
||||||
argcomplete_script=script,
|
|
||||||
completion_arg=completion_arg,
|
|
||||||
function_name=function_name,
|
|
||||||
)
|
|
||||||
elif shell == "powershell":
|
|
||||||
code = ""
|
|
||||||
for executable in executables:
|
|
||||||
script = argcomplete_script or executable
|
|
||||||
code += powershell_code % dict(executable=executable, argcomplete_script=script)
|
|
||||||
|
|
||||||
else:
|
|
||||||
code = ""
|
|
||||||
for executable in executables:
|
|
||||||
script = argcomplete_script
|
|
||||||
# If no script was specified, default to the executable being completed.
|
|
||||||
if not script:
|
|
||||||
script = executable
|
|
||||||
code += shell_codes.get(shell, "") % dict(executable=executable, argcomplete_script=script)
|
|
||||||
|
|
||||||
return code
|
|
|
@ -1 +0,0 @@
|
||||||
import os; var = 'SETUPTOOLS_USE_DISTUTILS'; enabled = os.environ.get(var, 'stdlib') == 'local'; enabled and __import__('_distutils_hack').add_shim();
|
|
|
@ -1 +0,0 @@
|
||||||
pip
|
|
|
@ -1,20 +0,0 @@
|
||||||
Copyright (c) 2008-present The pip developers (see AUTHORS.txt file)
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
a copy of this software and associated documentation files (the
|
|
||||||
"Software"), to deal in the Software without restriction, including
|
|
||||||
without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be
|
|
||||||
included in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
@ -1,92 +0,0 @@
|
||||||
Metadata-Version: 2.1
|
|
||||||
Name: pip
|
|
||||||
Version: 22.0.2
|
|
||||||
Summary: The PyPA recommended tool for installing Python packages.
|
|
||||||
Home-page: https://pip.pypa.io/
|
|
||||||
Author: The pip developers
|
|
||||||
Author-email: distutils-sig@python.org
|
|
||||||
License: MIT
|
|
||||||
Project-URL: Documentation, https://pip.pypa.io
|
|
||||||
Project-URL: Source, https://github.com/pypa/pip
|
|
||||||
Project-URL: Changelog, https://pip.pypa.io/en/stable/news/
|
|
||||||
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 :: 3
|
|
||||||
Classifier: Programming Language :: Python :: 3 :: Only
|
|
||||||
Classifier: Programming Language :: Python :: 3.7
|
|
||||||
Classifier: Programming Language :: Python :: 3.8
|
|
||||||
Classifier: Programming Language :: Python :: 3.9
|
|
||||||
Classifier: Programming Language :: Python :: 3.10
|
|
||||||
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
||||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
||||||
Requires-Python: >=3.7
|
|
||||||
License-File: LICENSE.txt
|
|
||||||
|
|
||||||
pip - The Python Package Installer
|
|
||||||
==================================
|
|
||||||
|
|
||||||
.. image:: https://img.shields.io/pypi/v/pip.svg
|
|
||||||
:target: https://pypi.org/project/pip/
|
|
||||||
|
|
||||||
.. image:: https://readthedocs.org/projects/pip/badge/?version=latest
|
|
||||||
:target: https://pip.pypa.io/en/latest
|
|
||||||
|
|
||||||
pip is the `package installer`_ for Python. You can use pip to install packages from the `Python Package Index`_ and other indexes.
|
|
||||||
|
|
||||||
Please take a look at our documentation for how to install and use pip:
|
|
||||||
|
|
||||||
* `Installation`_
|
|
||||||
* `Usage`_
|
|
||||||
|
|
||||||
We release updates regularly, with a new version every 3 months. Find more details in our documentation:
|
|
||||||
|
|
||||||
* `Release notes`_
|
|
||||||
* `Release process`_
|
|
||||||
|
|
||||||
In pip 20.3, we've `made a big improvement to the heart of pip`_; `learn more`_. We want your input, so `sign up for our user experience research studies`_ to help us do it right.
|
|
||||||
|
|
||||||
**Note**: pip 21.0, in January 2021, removed Python 2 support, per pip's `Python 2 support policy`_. Please migrate to Python 3.
|
|
||||||
|
|
||||||
If you find bugs, need help, or want to talk to the developers, please use our mailing lists or chat rooms:
|
|
||||||
|
|
||||||
* `Issue tracking`_
|
|
||||||
* `Discourse channel`_
|
|
||||||
* `User IRC`_
|
|
||||||
|
|
||||||
If you want to get involved head over to GitHub to get the source code, look at our development documentation and feel free to jump on the developer mailing lists and chat rooms:
|
|
||||||
|
|
||||||
* `GitHub page`_
|
|
||||||
* `Development documentation`_
|
|
||||||
* `Development mailing list`_
|
|
||||||
* `Development IRC`_
|
|
||||||
|
|
||||||
Code of Conduct
|
|
||||||
---------------
|
|
||||||
|
|
||||||
Everyone interacting in the pip project's codebases, issue trackers, chat
|
|
||||||
rooms, and mailing lists is expected to follow the `PSF Code of Conduct`_.
|
|
||||||
|
|
||||||
.. _package installer: https://packaging.python.org/guides/tool-recommendations/
|
|
||||||
.. _Python Package Index: https://pypi.org
|
|
||||||
.. _Installation: https://pip.pypa.io/en/stable/installation/
|
|
||||||
.. _Usage: https://pip.pypa.io/en/stable/
|
|
||||||
.. _Release notes: https://pip.pypa.io/en/stable/news.html
|
|
||||||
.. _Release process: https://pip.pypa.io/en/latest/development/release-process/
|
|
||||||
.. _GitHub page: https://github.com/pypa/pip
|
|
||||||
.. _Development documentation: https://pip.pypa.io/en/latest/development
|
|
||||||
.. _made a big improvement to the heart of pip: https://pyfound.blogspot.com/2020/11/pip-20-3-new-resolver.html
|
|
||||||
.. _learn more: https://pip.pypa.io/en/latest/user_guide/#changes-to-the-pip-dependency-resolver-in-20-3-2020
|
|
||||||
.. _sign up for our user experience research studies: https://pyfound.blogspot.com/2020/03/new-pip-resolver-to-roll-out-this-year.html
|
|
||||||
.. _Python 2 support policy: https://pip.pypa.io/en/latest/development/release-process/#python-2-support
|
|
||||||
.. _Issue tracking: https://github.com/pypa/pip/issues
|
|
||||||
.. _Discourse channel: https://discuss.python.org/c/packaging
|
|
||||||
.. _Development mailing list: https://mail.python.org/mailman3/lists/distutils-sig.python.org/
|
|
||||||
.. _User IRC: https://kiwiirc.com/nextclient/#ircs://irc.libera.chat:+6697/pypa
|
|
||||||
.. _Development IRC: https://kiwiirc.com/nextclient/#ircs://irc.libera.chat:+6697/pypa-dev
|
|
||||||
.. _PSF Code of Conduct: https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md
|
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,5 +0,0 @@
|
||||||
Wheel-Version: 1.0
|
|
||||||
Generator: bdist_wheel (0.37.1)
|
|
||||||
Root-Is-Purelib: true
|
|
||||||
Tag: py3-none-any
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
[console_scripts]
|
|
||||||
pip = pip._internal.cli.main:main
|
|
||||||
pip3 = pip._internal.cli.main:main
|
|
||||||
pip3.10 = pip._internal.cli.main:main
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
pip
|
|
|
@ -1,13 +0,0 @@
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
__version__ = "22.0.2"
|
|
||||||
|
|
||||||
|
|
||||||
def main(args: Optional[List[str]] = None) -> int:
|
|
||||||
"""This is an internal API only meant for use by pip's own console scripts.
|
|
||||||
|
|
||||||
For additional details, see https://github.com/pypa/pip/issues/7498.
|
|
||||||
"""
|
|
||||||
from pip._internal.utils.entrypoints import _wrapper
|
|
||||||
|
|
||||||
return _wrapper(args)
|
|
|
@ -1,31 +0,0 @@
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
# Remove '' and current working directory from the first entry
|
|
||||||
# of sys.path, if present to avoid using current directory
|
|
||||||
# in pip commands check, freeze, install, list and show,
|
|
||||||
# when invoked as python -m pip <command>
|
|
||||||
if sys.path[0] in ("", os.getcwd()):
|
|
||||||
sys.path.pop(0)
|
|
||||||
|
|
||||||
# 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)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
# Work around the error reported in #9540, pending a proper fix.
|
|
||||||
# Note: It is essential the warning filter is set *before* importing
|
|
||||||
# pip, as the deprecation happens at import time, not runtime.
|
|
||||||
warnings.filterwarnings(
|
|
||||||
"ignore", category=DeprecationWarning, module=".*packaging\\.version"
|
|
||||||
)
|
|
||||||
from pip._internal.cli.main import main as _main
|
|
||||||
|
|
||||||
sys.exit(_main())
|
|
Binary file not shown.
Binary file not shown.
|
@ -1,19 +0,0 @@
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
import pip._internal.utils.inject_securetransport # noqa
|
|
||||||
from pip._internal.utils import _log
|
|
||||||
|
|
||||||
# init_logging() must be called before any call to logging.getLogger()
|
|
||||||
# which happens at import of most modules.
|
|
||||||
_log.init_logging()
|
|
||||||
|
|
||||||
|
|
||||||
def main(args: (Optional[List[str]]) = None) -> int:
|
|
||||||
"""This is preserved for old console scripts that may still be referencing
|
|
||||||
it.
|
|
||||||
|
|
||||||
For additional details, see https://github.com/pypa/pip/issues/7498.
|
|
||||||
"""
|
|
||||||
from pip._internal.utils.entrypoints import _wrapper
|
|
||||||
|
|
||||||
return _wrapper(args)
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,296 +0,0 @@
|
||||||
"""Build Environment used for isolation during sdist building
|
|
||||||
"""
|
|
||||||
|
|
||||||
import contextlib
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import pathlib
|
|
||||||
import sys
|
|
||||||
import textwrap
|
|
||||||
import zipfile
|
|
||||||
from collections import OrderedDict
|
|
||||||
from sysconfig import get_paths
|
|
||||||
from types import TracebackType
|
|
||||||
from typing import TYPE_CHECKING, Iterable, Iterator, List, Optional, Set, Tuple, Type
|
|
||||||
|
|
||||||
from pip._vendor.certifi import where
|
|
||||||
from pip._vendor.packaging.requirements import Requirement
|
|
||||||
from pip._vendor.packaging.version import Version
|
|
||||||
|
|
||||||
from pip import __file__ as pip_location
|
|
||||||
from pip._internal.cli.spinners import open_spinner
|
|
||||||
from pip._internal.locations import get_platlib, get_prefixed_libs, get_purelib
|
|
||||||
from pip._internal.metadata import get_environment
|
|
||||||
from pip._internal.utils.subprocess import call_subprocess
|
|
||||||
from pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from pip._internal.index.package_finder import PackageFinder
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class _Prefix:
|
|
||||||
def __init__(self, path: str) -> None:
|
|
||||||
self.path = path
|
|
||||||
self.setup = False
|
|
||||||
self.bin_dir = get_paths(
|
|
||||||
"nt" if os.name == "nt" else "posix_prefix",
|
|
||||||
vars={"base": path, "platbase": path},
|
|
||||||
)["scripts"]
|
|
||||||
self.lib_dirs = get_prefixed_libs(path)
|
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def _create_standalone_pip() -> Iterator[str]:
|
|
||||||
"""Create a "standalone pip" zip file.
|
|
||||||
|
|
||||||
The zip file's content is identical to the currently-running pip.
|
|
||||||
It will be used to install requirements into the build environment.
|
|
||||||
"""
|
|
||||||
source = pathlib.Path(pip_location).resolve().parent
|
|
||||||
|
|
||||||
# Return the current instance if `source` is not a directory. We can't build
|
|
||||||
# a zip from this, and it likely means the instance is already standalone.
|
|
||||||
if not source.is_dir():
|
|
||||||
yield str(source)
|
|
||||||
return
|
|
||||||
|
|
||||||
with TempDirectory(kind="standalone-pip") as tmp_dir:
|
|
||||||
pip_zip = os.path.join(tmp_dir.path, "__env_pip__.zip")
|
|
||||||
kwargs = {}
|
|
||||||
if sys.version_info >= (3, 8):
|
|
||||||
kwargs["strict_timestamps"] = False
|
|
||||||
with zipfile.ZipFile(pip_zip, "w", **kwargs) as zf:
|
|
||||||
for child in source.rglob("*"):
|
|
||||||
zf.write(child, child.relative_to(source.parent).as_posix())
|
|
||||||
yield os.path.join(pip_zip, "pip")
|
|
||||||
|
|
||||||
|
|
||||||
class BuildEnvironment:
|
|
||||||
"""Creates and manages an isolated environment to install build deps"""
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
temp_dir = TempDirectory(kind=tempdir_kinds.BUILD_ENV, globally_managed=True)
|
|
||||||
|
|
||||||
self._prefixes = OrderedDict(
|
|
||||||
(name, _Prefix(os.path.join(temp_dir.path, name)))
|
|
||||||
for name in ("normal", "overlay")
|
|
||||||
)
|
|
||||||
|
|
||||||
self._bin_dirs: List[str] = []
|
|
||||||
self._lib_dirs: List[str] = []
|
|
||||||
for prefix in reversed(list(self._prefixes.values())):
|
|
||||||
self._bin_dirs.append(prefix.bin_dir)
|
|
||||||
self._lib_dirs.extend(prefix.lib_dirs)
|
|
||||||
|
|
||||||
# Customize site to:
|
|
||||||
# - ensure .pth files are honored
|
|
||||||
# - prevent access to system site packages
|
|
||||||
system_sites = {
|
|
||||||
os.path.normcase(site) for site in (get_purelib(), get_platlib())
|
|
||||||
}
|
|
||||||
self._site_dir = os.path.join(temp_dir.path, "site")
|
|
||||||
if not os.path.exists(self._site_dir):
|
|
||||||
os.mkdir(self._site_dir)
|
|
||||||
with open(
|
|
||||||
os.path.join(self._site_dir, "sitecustomize.py"), "w", encoding="utf-8"
|
|
||||||
) as fp:
|
|
||||||
fp.write(
|
|
||||||
textwrap.dedent(
|
|
||||||
"""
|
|
||||||
import os, site, sys
|
|
||||||
|
|
||||||
# First, drop system-sites related paths.
|
|
||||||
original_sys_path = sys.path[:]
|
|
||||||
known_paths = set()
|
|
||||||
for path in {system_sites!r}:
|
|
||||||
site.addsitedir(path, known_paths=known_paths)
|
|
||||||
system_paths = set(
|
|
||||||
os.path.normcase(path)
|
|
||||||
for path in sys.path[len(original_sys_path):]
|
|
||||||
)
|
|
||||||
original_sys_path = [
|
|
||||||
path for path in original_sys_path
|
|
||||||
if os.path.normcase(path) not in system_paths
|
|
||||||
]
|
|
||||||
sys.path = original_sys_path
|
|
||||||
|
|
||||||
# Second, add lib directories.
|
|
||||||
# ensuring .pth file are processed.
|
|
||||||
for path in {lib_dirs!r}:
|
|
||||||
assert not path in sys.path
|
|
||||||
site.addsitedir(path)
|
|
||||||
"""
|
|
||||||
).format(system_sites=system_sites, lib_dirs=self._lib_dirs)
|
|
||||||
)
|
|
||||||
|
|
||||||
def __enter__(self) -> None:
|
|
||||||
self._save_env = {
|
|
||||||
name: os.environ.get(name, None)
|
|
||||||
for name in ("PATH", "PYTHONNOUSERSITE", "PYTHONPATH")
|
|
||||||
}
|
|
||||||
|
|
||||||
path = self._bin_dirs[:]
|
|
||||||
old_path = self._save_env["PATH"]
|
|
||||||
if old_path:
|
|
||||||
path.extend(old_path.split(os.pathsep))
|
|
||||||
|
|
||||||
pythonpath = [self._site_dir]
|
|
||||||
|
|
||||||
os.environ.update(
|
|
||||||
{
|
|
||||||
"PATH": os.pathsep.join(path),
|
|
||||||
"PYTHONNOUSERSITE": "1",
|
|
||||||
"PYTHONPATH": os.pathsep.join(pythonpath),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
def __exit__(
|
|
||||||
self,
|
|
||||||
exc_type: Optional[Type[BaseException]],
|
|
||||||
exc_val: Optional[BaseException],
|
|
||||||
exc_tb: Optional[TracebackType],
|
|
||||||
) -> None:
|
|
||||||
for varname, old_value in self._save_env.items():
|
|
||||||
if old_value is None:
|
|
||||||
os.environ.pop(varname, None)
|
|
||||||
else:
|
|
||||||
os.environ[varname] = old_value
|
|
||||||
|
|
||||||
def check_requirements(
|
|
||||||
self, reqs: Iterable[str]
|
|
||||||
) -> Tuple[Set[Tuple[str, str]], Set[str]]:
|
|
||||||
"""Return 2 sets:
|
|
||||||
- conflicting requirements: set of (installed, wanted) reqs tuples
|
|
||||||
- missing requirements: set of reqs
|
|
||||||
"""
|
|
||||||
missing = set()
|
|
||||||
conflicting = set()
|
|
||||||
if reqs:
|
|
||||||
env = get_environment(self._lib_dirs)
|
|
||||||
for req_str in reqs:
|
|
||||||
req = Requirement(req_str)
|
|
||||||
dist = env.get_distribution(req.name)
|
|
||||||
if not dist:
|
|
||||||
missing.add(req_str)
|
|
||||||
continue
|
|
||||||
if isinstance(dist.version, Version):
|
|
||||||
installed_req_str = f"{req.name}=={dist.version}"
|
|
||||||
else:
|
|
||||||
installed_req_str = f"{req.name}==={dist.version}"
|
|
||||||
if dist.version not in req.specifier:
|
|
||||||
conflicting.add((installed_req_str, req_str))
|
|
||||||
# FIXME: Consider direct URL?
|
|
||||||
return conflicting, missing
|
|
||||||
|
|
||||||
def install_requirements(
|
|
||||||
self,
|
|
||||||
finder: "PackageFinder",
|
|
||||||
requirements: Iterable[str],
|
|
||||||
prefix_as_string: str,
|
|
||||||
*,
|
|
||||||
kind: str,
|
|
||||||
) -> None:
|
|
||||||
prefix = self._prefixes[prefix_as_string]
|
|
||||||
assert not prefix.setup
|
|
||||||
prefix.setup = True
|
|
||||||
if not requirements:
|
|
||||||
return
|
|
||||||
with contextlib.ExitStack() as ctx:
|
|
||||||
pip_runnable = ctx.enter_context(_create_standalone_pip())
|
|
||||||
self._install_requirements(
|
|
||||||
pip_runnable,
|
|
||||||
finder,
|
|
||||||
requirements,
|
|
||||||
prefix,
|
|
||||||
kind=kind,
|
|
||||||
)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _install_requirements(
|
|
||||||
pip_runnable: str,
|
|
||||||
finder: "PackageFinder",
|
|
||||||
requirements: Iterable[str],
|
|
||||||
prefix: _Prefix,
|
|
||||||
*,
|
|
||||||
kind: str,
|
|
||||||
) -> None:
|
|
||||||
args: List[str] = [
|
|
||||||
sys.executable,
|
|
||||||
pip_runnable,
|
|
||||||
"install",
|
|
||||||
"--ignore-installed",
|
|
||||||
"--no-user",
|
|
||||||
"--prefix",
|
|
||||||
prefix.path,
|
|
||||||
"--no-warn-script-location",
|
|
||||||
]
|
|
||||||
if logger.getEffectiveLevel() <= logging.DEBUG:
|
|
||||||
args.append("-v")
|
|
||||||
for format_control in ("no_binary", "only_binary"):
|
|
||||||
formats = getattr(finder.format_control, format_control)
|
|
||||||
args.extend(
|
|
||||||
(
|
|
||||||
"--" + format_control.replace("_", "-"),
|
|
||||||
",".join(sorted(formats or {":none:"})),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
index_urls = finder.index_urls
|
|
||||||
if index_urls:
|
|
||||||
args.extend(["-i", index_urls[0]])
|
|
||||||
for extra_index in index_urls[1:]:
|
|
||||||
args.extend(["--extra-index-url", extra_index])
|
|
||||||
else:
|
|
||||||
args.append("--no-index")
|
|
||||||
for link in finder.find_links:
|
|
||||||
args.extend(["--find-links", link])
|
|
||||||
|
|
||||||
for host in finder.trusted_hosts:
|
|
||||||
args.extend(["--trusted-host", host])
|
|
||||||
if finder.allow_all_prereleases:
|
|
||||||
args.append("--pre")
|
|
||||||
if finder.prefer_binary:
|
|
||||||
args.append("--prefer-binary")
|
|
||||||
args.append("--")
|
|
||||||
args.extend(requirements)
|
|
||||||
extra_environ = {"_PIP_STANDALONE_CERT": where()}
|
|
||||||
with open_spinner(f"Installing {kind}") as spinner:
|
|
||||||
call_subprocess(
|
|
||||||
args,
|
|
||||||
command_desc=f"pip subprocess to install {kind}",
|
|
||||||
spinner=spinner,
|
|
||||||
extra_environ=extra_environ,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class NoOpBuildEnvironment(BuildEnvironment):
|
|
||||||
"""A no-op drop-in replacement for BuildEnvironment"""
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def __enter__(self) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def __exit__(
|
|
||||||
self,
|
|
||||||
exc_type: Optional[Type[BaseException]],
|
|
||||||
exc_val: Optional[BaseException],
|
|
||||||
exc_tb: Optional[TracebackType],
|
|
||||||
) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def cleanup(self) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def install_requirements(
|
|
||||||
self,
|
|
||||||
finder: "PackageFinder",
|
|
||||||
requirements: Iterable[str],
|
|
||||||
prefix_as_string: str,
|
|
||||||
*,
|
|
||||||
kind: str,
|
|
||||||
) -> None:
|
|
||||||
raise NotImplementedError()
|
|
|
@ -1,264 +0,0 @@
|
||||||
"""Cache Management
|
|
||||||
"""
|
|
||||||
|
|
||||||
import hashlib
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
from typing import Any, Dict, List, Optional, Set
|
|
||||||
|
|
||||||
from pip._vendor.packaging.tags import Tag, interpreter_name, interpreter_version
|
|
||||||
from pip._vendor.packaging.utils import canonicalize_name
|
|
||||||
|
|
||||||
from pip._internal.exceptions import InvalidWheelFilename
|
|
||||||
from pip._internal.models.format_control import FormatControl
|
|
||||||
from pip._internal.models.link import Link
|
|
||||||
from pip._internal.models.wheel import Wheel
|
|
||||||
from pip._internal.utils.temp_dir import TempDirectory, tempdir_kinds
|
|
||||||
from pip._internal.utils.urls import path_to_url
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def _hash_dict(d: Dict[str, str]) -> str:
|
|
||||||
"""Return a stable sha224 of a dictionary."""
|
|
||||||
s = json.dumps(d, sort_keys=True, separators=(",", ":"), ensure_ascii=True)
|
|
||||||
return hashlib.sha224(s.encode("ascii")).hexdigest()
|
|
||||||
|
|
||||||
|
|
||||||
class Cache:
|
|
||||||
"""An abstract class - provides cache directories for data from links
|
|
||||||
|
|
||||||
|
|
||||||
:param cache_dir: The root of the cache.
|
|
||||||
:param format_control: An object of FormatControl class to limit
|
|
||||||
binaries being read from the cache.
|
|
||||||
:param allowed_formats: which formats of files the cache should store.
|
|
||||||
('binary' and 'source' are the only allowed values)
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self, cache_dir: str, format_control: FormatControl, allowed_formats: Set[str]
|
|
||||||
) -> None:
|
|
||||||
super().__init__()
|
|
||||||
assert not cache_dir or os.path.isabs(cache_dir)
|
|
||||||
self.cache_dir = cache_dir or 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: Link) -> List[str]:
|
|
||||||
"""Get parts of part that must be os.path.joined with cache_dir"""
|
|
||||||
|
|
||||||
# We want to generate an url to use as our cache key, we don't want to
|
|
||||||
# just re-use the URL because it might have other items in the fragment
|
|
||||||
# and we don't care about those.
|
|
||||||
key_parts = {"url": link.url_without_fragment}
|
|
||||||
if link.hash_name is not None and link.hash is not None:
|
|
||||||
key_parts[link.hash_name] = link.hash
|
|
||||||
if link.subdirectory_fragment:
|
|
||||||
key_parts["subdirectory"] = link.subdirectory_fragment
|
|
||||||
|
|
||||||
# Include interpreter name, major and minor version in cache key
|
|
||||||
# to cope with ill-behaved sdists that build a different wheel
|
|
||||||
# depending on the python version their setup.py is being run on,
|
|
||||||
# and don't encode the difference in compatibility tags.
|
|
||||||
# https://github.com/pypa/pip/issues/7296
|
|
||||||
key_parts["interpreter_name"] = interpreter_name()
|
|
||||||
key_parts["interpreter_version"] = interpreter_version()
|
|
||||||
|
|
||||||
# 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 = _hash_dict(key_parts)
|
|
||||||
|
|
||||||
# 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: Link, canonical_package_name: str) -> List[Any]:
|
|
||||||
can_not_cache = not self.cache_dir or not canonical_package_name or not link
|
|
||||||
if can_not_cache:
|
|
||||||
return []
|
|
||||||
|
|
||||||
formats = self.format_control.get_allowed_formats(canonical_package_name)
|
|
||||||
if not self.allowed_formats.intersection(formats):
|
|
||||||
return []
|
|
||||||
|
|
||||||
candidates = []
|
|
||||||
path = self.get_path_for_link(link)
|
|
||||||
if os.path.isdir(path):
|
|
||||||
for candidate in os.listdir(path):
|
|
||||||
candidates.append((candidate, path))
|
|
||||||
return candidates
|
|
||||||
|
|
||||||
def get_path_for_link(self, link: Link) -> str:
|
|
||||||
"""Return a directory to store cached items in for link."""
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
def get(
|
|
||||||
self,
|
|
||||||
link: Link,
|
|
||||||
package_name: Optional[str],
|
|
||||||
supported_tags: List[Tag],
|
|
||||||
) -> Link:
|
|
||||||
"""Returns a link to a cached item if it exists, otherwise returns the
|
|
||||||
passed link.
|
|
||||||
"""
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
|
|
||||||
class SimpleWheelCache(Cache):
|
|
||||||
"""A cache of wheels for future installs."""
|
|
||||||
|
|
||||||
def __init__(self, cache_dir: str, format_control: FormatControl) -> None:
|
|
||||||
super().__init__(cache_dir, format_control, {"binary"})
|
|
||||||
|
|
||||||
def get_path_for_link(self, link: Link) -> str:
|
|
||||||
"""Return a directory to store cached wheels for link
|
|
||||||
|
|
||||||
Because there are M wheels for any one sdist, we provide a directory
|
|
||||||
to cache them in, and then consult that directory when looking up
|
|
||||||
cache hits.
|
|
||||||
|
|
||||||
We only insert things into the cache if they have plausible version
|
|
||||||
numbers, so that we don't contaminate the cache with things that were
|
|
||||||
not unique. E.g. ./package might have dozens of installs done for it
|
|
||||||
and build a version of 0.0...and if we built and cached a wheel, we'd
|
|
||||||
end up using the same wheel even if the source has been edited.
|
|
||||||
|
|
||||||
:param link: The link of the sdist for which this will cache wheels.
|
|
||||||
"""
|
|
||||||
parts = self._get_cache_path_parts(link)
|
|
||||||
assert self.cache_dir
|
|
||||||
# Store wheels within the root cache_dir
|
|
||||||
return os.path.join(self.cache_dir, "wheels", *parts)
|
|
||||||
|
|
||||||
def get(
|
|
||||||
self,
|
|
||||||
link: Link,
|
|
||||||
package_name: Optional[str],
|
|
||||||
supported_tags: List[Tag],
|
|
||||||
) -> Link:
|
|
||||||
candidates = []
|
|
||||||
|
|
||||||
if not package_name:
|
|
||||||
return link
|
|
||||||
|
|
||||||
canonical_package_name = canonicalize_name(package_name)
|
|
||||||
for wheel_name, wheel_dir in self._get_candidates(link, canonical_package_name):
|
|
||||||
try:
|
|
||||||
wheel = Wheel(wheel_name)
|
|
||||||
except InvalidWheelFilename:
|
|
||||||
continue
|
|
||||||
if canonicalize_name(wheel.name) != canonical_package_name:
|
|
||||||
logger.debug(
|
|
||||||
"Ignoring cached wheel %s for %s as it "
|
|
||||||
"does not match the expected distribution name %s.",
|
|
||||||
wheel_name,
|
|
||||||
link,
|
|
||||||
package_name,
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
if not wheel.supported(supported_tags):
|
|
||||||
# Built for a different python/arch/etc
|
|
||||||
continue
|
|
||||||
candidates.append(
|
|
||||||
(
|
|
||||||
wheel.support_index_min(supported_tags),
|
|
||||||
wheel_name,
|
|
||||||
wheel_dir,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if not candidates:
|
|
||||||
return link
|
|
||||||
|
|
||||||
_, wheel_name, wheel_dir = min(candidates)
|
|
||||||
return Link(path_to_url(os.path.join(wheel_dir, wheel_name)))
|
|
||||||
|
|
||||||
|
|
||||||
class EphemWheelCache(SimpleWheelCache):
|
|
||||||
"""A SimpleWheelCache that creates it's own temporary cache directory"""
|
|
||||||
|
|
||||||
def __init__(self, format_control: FormatControl) -> None:
|
|
||||||
self._temp_dir = TempDirectory(
|
|
||||||
kind=tempdir_kinds.EPHEM_WHEEL_CACHE,
|
|
||||||
globally_managed=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
super().__init__(self._temp_dir.path, format_control)
|
|
||||||
|
|
||||||
|
|
||||||
class CacheEntry:
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
link: Link,
|
|
||||||
persistent: bool,
|
|
||||||
):
|
|
||||||
self.link = link
|
|
||||||
self.persistent = persistent
|
|
||||||
|
|
||||||
|
|
||||||
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: str, format_control: FormatControl) -> None:
|
|
||||||
super().__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: Link) -> str:
|
|
||||||
return self._wheel_cache.get_path_for_link(link)
|
|
||||||
|
|
||||||
def get_ephem_path_for_link(self, link: Link) -> str:
|
|
||||||
return self._ephem_cache.get_path_for_link(link)
|
|
||||||
|
|
||||||
def get(
|
|
||||||
self,
|
|
||||||
link: Link,
|
|
||||||
package_name: Optional[str],
|
|
||||||
supported_tags: List[Tag],
|
|
||||||
) -> Link:
|
|
||||||
cache_entry = self.get_cache_entry(link, package_name, supported_tags)
|
|
||||||
if cache_entry is None:
|
|
||||||
return link
|
|
||||||
return cache_entry.link
|
|
||||||
|
|
||||||
def get_cache_entry(
|
|
||||||
self,
|
|
||||||
link: Link,
|
|
||||||
package_name: Optional[str],
|
|
||||||
supported_tags: List[Tag],
|
|
||||||
) -> Optional[CacheEntry]:
|
|
||||||
"""Returns a CacheEntry with a link to a cached item if it exists or
|
|
||||||
None. The cache entry indicates if the item was found in the persistent
|
|
||||||
or ephemeral cache.
|
|
||||||
"""
|
|
||||||
retval = self._wheel_cache.get(
|
|
||||||
link=link,
|
|
||||||
package_name=package_name,
|
|
||||||
supported_tags=supported_tags,
|
|
||||||
)
|
|
||||||
if retval is not link:
|
|
||||||
return CacheEntry(retval, persistent=True)
|
|
||||||
|
|
||||||
retval = self._ephem_cache.get(
|
|
||||||
link=link,
|
|
||||||
package_name=package_name,
|
|
||||||
supported_tags=supported_tags,
|
|
||||||
)
|
|
||||||
if retval is not link:
|
|
||||||
return CacheEntry(retval, persistent=False)
|
|
||||||
|
|
||||||
return None
|
|
|
@ -1,4 +0,0 @@
|
||||||
"""Subpackage containing all of pip's command line interface related code
|
|
||||||
"""
|
|
||||||
|
|
||||||
# This file intentionally does not import submodules
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,171 +0,0 @@
|
||||||
"""Logic that powers autocompletion installed by ``pip completion``.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import optparse
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
from itertools import chain
|
|
||||||
from typing import Any, Iterable, List, Optional
|
|
||||||
|
|
||||||
from pip._internal.cli.main_parser import create_main_parser
|
|
||||||
from pip._internal.commands import commands_dict, create_command
|
|
||||||
from pip._internal.metadata import get_default_environment
|
|
||||||
|
|
||||||
|
|
||||||
def autocomplete() -> None:
|
|
||||||
"""Entry Point for completion of main and subcommand options."""
|
|
||||||
# Don't complete if user hasn't sourced bash_completion file.
|
|
||||||
if "PIP_AUTO_COMPLETE" not in os.environ:
|
|
||||||
return
|
|
||||||
cwords = os.environ["COMP_WORDS"].split()[1:]
|
|
||||||
cword = int(os.environ["COMP_CWORD"])
|
|
||||||
try:
|
|
||||||
current = cwords[cword - 1]
|
|
||||||
except IndexError:
|
|
||||||
current = ""
|
|
||||||
|
|
||||||
parser = create_main_parser()
|
|
||||||
subcommands = list(commands_dict)
|
|
||||||
options = []
|
|
||||||
|
|
||||||
# subcommand
|
|
||||||
subcommand_name: Optional[str] = None
|
|
||||||
for word in cwords:
|
|
||||||
if word in subcommands:
|
|
||||||
subcommand_name = word
|
|
||||||
break
|
|
||||||
# subcommand options
|
|
||||||
if subcommand_name is not None:
|
|
||||||
# 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 = not current.startswith("-") and subcommand_name in [
|
|
||||||
"show",
|
|
||||||
"uninstall",
|
|
||||||
]
|
|
||||||
if should_list_installed:
|
|
||||||
env = get_default_environment()
|
|
||||||
lc = current.lower()
|
|
||||||
installed = [
|
|
||||||
dist.canonical_name
|
|
||||||
for dist in env.iter_installed_distributions(local_only=True)
|
|
||||||
if dist.canonical_name.startswith(lc)
|
|
||||||
and dist.canonical_name not in cwords[1:]
|
|
||||||
]
|
|
||||||
# if there are no dists installed, fall back to option completion
|
|
||||||
if installed:
|
|
||||||
for dist in installed:
|
|
||||||
print(dist)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
should_list_installables = (
|
|
||||||
not current.startswith("-") and subcommand_name == "install"
|
|
||||||
)
|
|
||||||
if should_list_installables:
|
|
||||||
for path in auto_complete_paths(current, "path"):
|
|
||||||
print(path)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
subcommand = create_command(subcommand_name)
|
|
||||||
|
|
||||||
for opt in subcommand.parser.option_list_all:
|
|
||||||
if opt.help != optparse.SUPPRESS_HELP:
|
|
||||||
for opt_str in opt._long_opts + opt._short_opts:
|
|
||||||
options.append((opt_str, opt.nargs))
|
|
||||||
|
|
||||||
# filter out previously specified options from available options
|
|
||||||
prev_opts = [x.split("=")[0] for x in cwords[1 : cword - 1]]
|
|
||||||
options = [(x, v) for (x, v) in options if x not in prev_opts]
|
|
||||||
# filter options by current input
|
|
||||||
options = [(k, v) for k, v in options if k.startswith(current)]
|
|
||||||
# get completion type given cwords and available subcommand options
|
|
||||||
completion_type = get_path_completion_type(
|
|
||||||
cwords,
|
|
||||||
cword,
|
|
||||||
subcommand.parser.option_list_all,
|
|
||||||
)
|
|
||||||
# get completion files and directories if ``completion_type`` is
|
|
||||||
# ``<file>``, ``<dir>`` or ``<path>``
|
|
||||||
if completion_type:
|
|
||||||
paths = auto_complete_paths(current, completion_type)
|
|
||||||
options = [(path, 0) for path in paths]
|
|
||||||
for option in options:
|
|
||||||
opt_label = option[0]
|
|
||||||
# append '=' to options which require args
|
|
||||||
if option[1] and option[0][:2] == "--":
|
|
||||||
opt_label += "="
|
|
||||||
print(opt_label)
|
|
||||||
else:
|
|
||||||
# show main parser options only when necessary
|
|
||||||
|
|
||||||
opts = [i.option_list for i in parser.option_groups]
|
|
||||||
opts.append(parser.option_list)
|
|
||||||
flattened_opts = chain.from_iterable(opts)
|
|
||||||
if current.startswith("-"):
|
|
||||||
for opt in flattened_opts:
|
|
||||||
if opt.help != optparse.SUPPRESS_HELP:
|
|
||||||
subcommands += opt._long_opts + opt._short_opts
|
|
||||||
else:
|
|
||||||
# get completion type given cwords and all available options
|
|
||||||
completion_type = get_path_completion_type(cwords, cword, flattened_opts)
|
|
||||||
if completion_type:
|
|
||||||
subcommands = list(auto_complete_paths(current, completion_type))
|
|
||||||
|
|
||||||
print(" ".join([x for x in subcommands if x.startswith(current)]))
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
def get_path_completion_type(
|
|
||||||
cwords: List[str], cword: int, opts: Iterable[Any]
|
|
||||||
) -> Optional[str]:
|
|
||||||
"""Get the type of path completion (``file``, ``dir``, ``path`` or None)
|
|
||||||
|
|
||||||
:param cwords: same as the environmental variable ``COMP_WORDS``
|
|
||||||
:param cword: same as the environmental variable ``COMP_CWORD``
|
|
||||||
:param opts: The available options to check
|
|
||||||
:return: path completion type (``file``, ``dir``, ``path`` or None)
|
|
||||||
"""
|
|
||||||
if cword < 2 or not cwords[cword - 2].startswith("-"):
|
|
||||||
return None
|
|
||||||
for opt in opts:
|
|
||||||
if opt.help == optparse.SUPPRESS_HELP:
|
|
||||||
continue
|
|
||||||
for o in str(opt).split("/"):
|
|
||||||
if cwords[cword - 2].split("=")[0] == o:
|
|
||||||
if not opt.metavar or any(
|
|
||||||
x in ("path", "file", "dir") for x in opt.metavar.split("/")
|
|
||||||
):
|
|
||||||
return opt.metavar
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def auto_complete_paths(current: str, completion_type: str) -> Iterable[str]:
|
|
||||||
"""If ``completion_type`` is ``file`` or ``path``, list all regular files
|
|
||||||
and directories starting with ``current``; otherwise only list directories
|
|
||||||
starting with ``current``.
|
|
||||||
|
|
||||||
:param current: The word to be completed
|
|
||||||
:param completion_type: path completion type(``file``, ``path`` or ``dir``)
|
|
||||||
:return: A generator of regular files and/or directories
|
|
||||||
"""
|
|
||||||
directory, filename = os.path.split(current)
|
|
||||||
current_path = os.path.abspath(directory)
|
|
||||||
# Don't complete paths if they can't be accessed
|
|
||||||
if not os.access(current_path, os.R_OK):
|
|
||||||
return
|
|
||||||
filename = os.path.normcase(filename)
|
|
||||||
# list all files that start with ``filename``
|
|
||||||
file_list = (
|
|
||||||
x for x in os.listdir(current_path) if os.path.normcase(x).startswith(filename)
|
|
||||||
)
|
|
||||||
for f in file_list:
|
|
||||||
opt = os.path.join(current_path, f)
|
|
||||||
comp_file = os.path.normcase(os.path.join(directory, f))
|
|
||||||
# complete regular files when there is not ``<dir>`` after option
|
|
||||||
# complete directories when there is ``<file>``, ``<path>`` or
|
|
||||||
# ``<dir>``after option
|
|
||||||
if completion_type != "dir" and os.path.isfile(opt):
|
|
||||||
yield comp_file
|
|
||||||
elif os.path.isdir(opt):
|
|
||||||
yield os.path.join(comp_file, "")
|
|
|
@ -1,220 +0,0 @@
|
||||||
"""Base Command class, and related routines"""
|
|
||||||
|
|
||||||
import functools
|
|
||||||
import logging
|
|
||||||
import logging.config
|
|
||||||
import optparse
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import traceback
|
|
||||||
from optparse import Values
|
|
||||||
from typing import Any, Callable, List, Optional, Tuple
|
|
||||||
|
|
||||||
from pip._internal.cli import cmdoptions
|
|
||||||
from pip._internal.cli.command_context import CommandContextMixIn
|
|
||||||
from pip._internal.cli.parser import ConfigOptionParser, UpdatingDefaultsHelpFormatter
|
|
||||||
from pip._internal.cli.status_codes import (
|
|
||||||
ERROR,
|
|
||||||
PREVIOUS_BUILD_DIR_ERROR,
|
|
||||||
UNKNOWN_ERROR,
|
|
||||||
VIRTUALENV_NOT_FOUND,
|
|
||||||
)
|
|
||||||
from pip._internal.exceptions import (
|
|
||||||
BadCommand,
|
|
||||||
CommandError,
|
|
||||||
DiagnosticPipError,
|
|
||||||
InstallationError,
|
|
||||||
NetworkConnectionError,
|
|
||||||
PreviousBuildDirError,
|
|
||||||
UninstallationError,
|
|
||||||
)
|
|
||||||
from pip._internal.utils.filesystem import check_path_owner
|
|
||||||
from pip._internal.utils.logging import BrokenStdoutLoggingError, setup_logging
|
|
||||||
from pip._internal.utils.misc import get_prog, normalize_path
|
|
||||||
from pip._internal.utils.temp_dir import TempDirectoryTypeRegistry as TempDirRegistry
|
|
||||||
from pip._internal.utils.temp_dir import global_tempdir_manager, tempdir_registry
|
|
||||||
from pip._internal.utils.virtualenv import running_under_virtualenv
|
|
||||||
|
|
||||||
__all__ = ["Command"]
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class Command(CommandContextMixIn):
|
|
||||||
usage: str = ""
|
|
||||||
ignore_require_venv: bool = False
|
|
||||||
|
|
||||||
def __init__(self, name: str, summary: str, isolated: bool = False) -> None:
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
self.name = name
|
|
||||||
self.summary = summary
|
|
||||||
self.parser = ConfigOptionParser(
|
|
||||||
usage=self.usage,
|
|
||||||
prog=f"{get_prog()} {name}",
|
|
||||||
formatter=UpdatingDefaultsHelpFormatter(),
|
|
||||||
add_help_option=False,
|
|
||||||
name=name,
|
|
||||||
description=self.__doc__,
|
|
||||||
isolated=isolated,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.tempdir_registry: Optional[TempDirRegistry] = None
|
|
||||||
|
|
||||||
# Commands should add options to this option group
|
|
||||||
optgroup_name = f"{self.name.capitalize()} Options"
|
|
||||||
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)
|
|
||||||
|
|
||||||
self.add_options()
|
|
||||||
|
|
||||||
def add_options(self) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def handle_pip_version_check(self, options: Values) -> None:
|
|
||||||
"""
|
|
||||||
This is a no-op so that commands by default do not do the pip version
|
|
||||||
check.
|
|
||||||
"""
|
|
||||||
# Make sure we do the pip version check if the index_group options
|
|
||||||
# are present.
|
|
||||||
assert not hasattr(options, "no_index")
|
|
||||||
|
|
||||||
def run(self, options: Values, args: List[str]) -> int:
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def parse_args(self, args: List[str]) -> Tuple[Values, List[str]]:
|
|
||||||
# factored out for testability
|
|
||||||
return self.parser.parse_args(args)
|
|
||||||
|
|
||||||
def main(self, args: List[str]) -> int:
|
|
||||||
try:
|
|
||||||
with self.main_context():
|
|
||||||
return self._main(args)
|
|
||||||
finally:
|
|
||||||
logging.shutdown()
|
|
||||||
|
|
||||||
def _main(self, args: List[str]) -> int:
|
|
||||||
# We must initialize this before the tempdir manager, otherwise the
|
|
||||||
# configuration would not be accessible by the time we clean up the
|
|
||||||
# tempdir manager.
|
|
||||||
self.tempdir_registry = self.enter_context(tempdir_registry())
|
|
||||||
# Intentionally set as early as possible so globally-managed temporary
|
|
||||||
# directories are available to the rest of the code.
|
|
||||||
self.enter_context(global_tempdir_manager())
|
|
||||||
|
|
||||||
options, args = self.parse_args(args)
|
|
||||||
|
|
||||||
# Set verbosity so that it can be used elsewhere.
|
|
||||||
self.verbosity = options.verbose - options.quiet
|
|
||||||
|
|
||||||
level_number = setup_logging(
|
|
||||||
verbosity=self.verbosity,
|
|
||||||
no_color=options.no_color,
|
|
||||||
user_log_file=options.log,
|
|
||||||
)
|
|
||||||
|
|
||||||
# TODO: Try to get these passing down from the command?
|
|
||||||
# without resorting to os.environ to hold these.
|
|
||||||
# This also affects isolated builds and it should.
|
|
||||||
|
|
||||||
if options.no_input:
|
|
||||||
os.environ["PIP_NO_INPUT"] = "1"
|
|
||||||
|
|
||||||
if options.exists_action:
|
|
||||||
os.environ["PIP_EXISTS_ACTION"] = " ".join(options.exists_action)
|
|
||||||
|
|
||||||
if options.require_venv and not self.ignore_require_venv:
|
|
||||||
# If a venv is required check if it can really be found
|
|
||||||
if not running_under_virtualenv():
|
|
||||||
logger.critical("Could not find an activated virtualenv (required).")
|
|
||||||
sys.exit(VIRTUALENV_NOT_FOUND)
|
|
||||||
|
|
||||||
if options.cache_dir:
|
|
||||||
options.cache_dir = normalize_path(options.cache_dir)
|
|
||||||
if not check_path_owner(options.cache_dir):
|
|
||||||
logger.warning(
|
|
||||||
"The directory '%s' or its parent directory is not owned "
|
|
||||||
"or is not writable by the current user. The cache "
|
|
||||||
"has been disabled. Check the permissions and owner of "
|
|
||||||
"that directory. If executing pip with sudo, you should "
|
|
||||||
"use sudo's -H flag.",
|
|
||||||
options.cache_dir,
|
|
||||||
)
|
|
||||||
options.cache_dir = None
|
|
||||||
|
|
||||||
if "2020-resolver" in options.features_enabled:
|
|
||||||
logger.warning(
|
|
||||||
"--use-feature=2020-resolver no longer has any effect, "
|
|
||||||
"since it is now the default dependency resolver in pip. "
|
|
||||||
"This will become an error in pip 21.0."
|
|
||||||
)
|
|
||||||
|
|
||||||
def intercepts_unhandled_exc(
|
|
||||||
run_func: Callable[..., int]
|
|
||||||
) -> Callable[..., int]:
|
|
||||||
@functools.wraps(run_func)
|
|
||||||
def exc_logging_wrapper(*args: Any) -> int:
|
|
||||||
try:
|
|
||||||
status = run_func(*args)
|
|
||||||
assert isinstance(status, int)
|
|
||||||
return status
|
|
||||||
except DiagnosticPipError as exc:
|
|
||||||
logger.error("[present-diagnostic] %s", exc)
|
|
||||||
logger.debug("Exception information:", exc_info=True)
|
|
||||||
|
|
||||||
return ERROR
|
|
||||||
except PreviousBuildDirError as exc:
|
|
||||||
logger.critical(str(exc))
|
|
||||||
logger.debug("Exception information:", exc_info=True)
|
|
||||||
|
|
||||||
return PREVIOUS_BUILD_DIR_ERROR
|
|
||||||
except (
|
|
||||||
InstallationError,
|
|
||||||
UninstallationError,
|
|
||||||
BadCommand,
|
|
||||||
NetworkConnectionError,
|
|
||||||
) as exc:
|
|
||||||
logger.critical(str(exc))
|
|
||||||
logger.debug("Exception information:", exc_info=True)
|
|
||||||
|
|
||||||
return ERROR
|
|
||||||
except CommandError as exc:
|
|
||||||
logger.critical("%s", exc)
|
|
||||||
logger.debug("Exception information:", exc_info=True)
|
|
||||||
|
|
||||||
return ERROR
|
|
||||||
except BrokenStdoutLoggingError:
|
|
||||||
# Bypass our logger and write any remaining messages to
|
|
||||||
# stderr because stdout no longer works.
|
|
||||||
print("ERROR: Pipe to stdout was broken", file=sys.stderr)
|
|
||||||
if level_number <= logging.DEBUG:
|
|
||||||
traceback.print_exc(file=sys.stderr)
|
|
||||||
|
|
||||||
return ERROR
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
logger.critical("Operation cancelled by user")
|
|
||||||
logger.debug("Exception information:", exc_info=True)
|
|
||||||
|
|
||||||
return ERROR
|
|
||||||
except BaseException:
|
|
||||||
logger.critical("Exception:", exc_info=True)
|
|
||||||
|
|
||||||
return UNKNOWN_ERROR
|
|
||||||
|
|
||||||
return exc_logging_wrapper
|
|
||||||
|
|
||||||
try:
|
|
||||||
if not options.debug_mode:
|
|
||||||
run = intercepts_unhandled_exc(self.run)
|
|
||||||
else:
|
|
||||||
run = self.run
|
|
||||||
return run(options, args)
|
|
||||||
finally:
|
|
||||||
self.handle_pip_version_check(options)
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,27 +0,0 @@
|
||||||
from contextlib import ExitStack, contextmanager
|
|
||||||
from typing import ContextManager, Iterator, TypeVar
|
|
||||||
|
|
||||||
_T = TypeVar("_T", covariant=True)
|
|
||||||
|
|
||||||
|
|
||||||
class CommandContextMixIn:
|
|
||||||
def __init__(self) -> None:
|
|
||||||
super().__init__()
|
|
||||||
self._in_main_context = False
|
|
||||||
self._main_context = ExitStack()
|
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def main_context(self) -> Iterator[None]:
|
|
||||||
assert not self._in_main_context
|
|
||||||
|
|
||||||
self._in_main_context = True
|
|
||||||
try:
|
|
||||||
with self._main_context:
|
|
||||||
yield
|
|
||||||
finally:
|
|
||||||
self._in_main_context = False
|
|
||||||
|
|
||||||
def enter_context(self, context_provider: ContextManager[_T]) -> _T:
|
|
||||||
assert self._in_main_context
|
|
||||||
|
|
||||||
return self._main_context.enter_context(context_provider)
|
|
|
@ -1,70 +0,0 @@
|
||||||
"""Primary application entrypoint.
|
|
||||||
"""
|
|
||||||
import locale
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
from pip._internal.cli.autocompletion import autocomplete
|
|
||||||
from pip._internal.cli.main_parser import parse_command
|
|
||||||
from pip._internal.commands import create_command
|
|
||||||
from pip._internal.exceptions import PipError
|
|
||||||
from pip._internal.utils import deprecation
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
# Do not import and use main() directly! Using it directly is actively
|
|
||||||
# discouraged by pip's maintainers. The name, location and behavior of
|
|
||||||
# this function is subject to change, so calling it directly is not
|
|
||||||
# portable across different pip versions.
|
|
||||||
|
|
||||||
# In addition, running pip in-process is unsupported and unsafe. This is
|
|
||||||
# elaborated in detail at
|
|
||||||
# https://pip.pypa.io/en/stable/user_guide/#using-pip-from-your-program.
|
|
||||||
# That document also provides suggestions that should work for nearly
|
|
||||||
# all users that are considering importing and using main() directly.
|
|
||||||
|
|
||||||
# However, we know that certain users will still want to invoke pip
|
|
||||||
# in-process. If you understand and accept the implications of using pip
|
|
||||||
# in an unsupported manner, the best approach is to use runpy to avoid
|
|
||||||
# depending on the exact location of this entry point.
|
|
||||||
|
|
||||||
# The following example shows how to use runpy to invoke pip in that
|
|
||||||
# case:
|
|
||||||
#
|
|
||||||
# sys.argv = ["pip", your, args, here]
|
|
||||||
# runpy.run_module("pip", run_name="__main__")
|
|
||||||
#
|
|
||||||
# Note that this will exit the process after running, unlike a direct
|
|
||||||
# call to main. As it is not safe to do any processing after calling
|
|
||||||
# main, this should not be an issue in practice.
|
|
||||||
|
|
||||||
|
|
||||||
def main(args: Optional[List[str]] = None) -> int:
|
|
||||||
if args is None:
|
|
||||||
args = sys.argv[1:]
|
|
||||||
|
|
||||||
# Configure our deprecation warnings to be sent through loggers
|
|
||||||
deprecation.install_warning_logger()
|
|
||||||
|
|
||||||
autocomplete()
|
|
||||||
|
|
||||||
try:
|
|
||||||
cmd_name, cmd_args = parse_command(args)
|
|
||||||
except PipError as exc:
|
|
||||||
sys.stderr.write(f"ERROR: {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 = create_command(cmd_name, isolated=("--isolated" in cmd_args))
|
|
||||||
|
|
||||||
return command.main(cmd_args)
|
|
|
@ -1,87 +0,0 @@
|
||||||
"""A single place for constructing and exposing the main parser
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
from typing import List, Tuple
|
|
||||||
|
|
||||||
from pip._internal.cli import cmdoptions
|
|
||||||
from pip._internal.cli.parser import ConfigOptionParser, UpdatingDefaultsHelpFormatter
|
|
||||||
from pip._internal.commands import commands_dict, get_similar_commands
|
|
||||||
from pip._internal.exceptions import CommandError
|
|
||||||
from pip._internal.utils.misc import get_pip_version, get_prog
|
|
||||||
|
|
||||||
__all__ = ["create_main_parser", "parse_command"]
|
|
||||||
|
|
||||||
|
|
||||||
def create_main_parser() -> ConfigOptionParser:
|
|
||||||
"""Creates and returns the main parser for pip's CLI"""
|
|
||||||
|
|
||||||
parser = ConfigOptionParser(
|
|
||||||
usage="\n%prog <command> [options]",
|
|
||||||
add_help_option=False,
|
|
||||||
formatter=UpdatingDefaultsHelpFormatter(),
|
|
||||||
name="global",
|
|
||||||
prog=get_prog(),
|
|
||||||
)
|
|
||||||
parser.disable_interspersed_args()
|
|
||||||
|
|
||||||
parser.version = get_pip_version()
|
|
||||||
|
|
||||||
# add the general options
|
|
||||||
gen_opts = cmdoptions.make_option_group(cmdoptions.general_group, parser)
|
|
||||||
parser.add_option_group(gen_opts)
|
|
||||||
|
|
||||||
# so the help formatter knows
|
|
||||||
parser.main = True # type: ignore
|
|
||||||
|
|
||||||
# create command listing for description
|
|
||||||
description = [""] + [
|
|
||||||
f"{name:27} {command_info.summary}"
|
|
||||||
for name, command_info in commands_dict.items()
|
|
||||||
]
|
|
||||||
parser.description = "\n".join(description)
|
|
||||||
|
|
||||||
return parser
|
|
||||||
|
|
||||||
|
|
||||||
def parse_command(args: List[str]) -> Tuple[str, List[str]]:
|
|
||||||
parser = create_main_parser()
|
|
||||||
|
|
||||||
# Note: parser calls disable_interspersed_args(), so the result of this
|
|
||||||
# call is to split the initial args into the general options before the
|
|
||||||
# subcommand and everything else.
|
|
||||||
# For example:
|
|
||||||
# args: ['--timeout=5', 'install', '--user', 'INITools']
|
|
||||||
# general_options: ['--timeout==5']
|
|
||||||
# args_else: ['install', '--user', 'INITools']
|
|
||||||
general_options, args_else = parser.parse_args(args)
|
|
||||||
|
|
||||||
# --version
|
|
||||||
if general_options.version:
|
|
||||||
sys.stdout.write(parser.version)
|
|
||||||
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 = [f'unknown command "{cmd_name}"']
|
|
||||||
if guess:
|
|
||||||
msg.append(f'maybe you meant "{guess}"')
|
|
||||||
|
|
||||||
raise CommandError(" - ".join(msg))
|
|
||||||
|
|
||||||
# all the args without the subcommand
|
|
||||||
cmd_args = args[:]
|
|
||||||
cmd_args.remove(cmd_name)
|
|
||||||
|
|
||||||
return cmd_name, cmd_args
|
|
|
@ -1,292 +0,0 @@
|
||||||
"""Base option parser setup"""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import optparse
|
|
||||||
import shutil
|
|
||||||
import sys
|
|
||||||
import textwrap
|
|
||||||
from contextlib import suppress
|
|
||||||
from typing import Any, Dict, Iterator, List, Tuple
|
|
||||||
|
|
||||||
from pip._internal.cli.status_codes import UNKNOWN_ERROR
|
|
||||||
from pip._internal.configuration import Configuration, ConfigurationError
|
|
||||||
from pip._internal.utils.misc import redact_auth_from_url, strtobool
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class PrettyHelpFormatter(optparse.IndentedHelpFormatter):
|
|
||||||
"""A prettier/less verbose help formatter for optparse."""
|
|
||||||
|
|
||||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
||||||
# help position must be aligned with __init__.parseopts.description
|
|
||||||
kwargs["max_help_position"] = 30
|
|
||||||
kwargs["indent_increment"] = 1
|
|
||||||
kwargs["width"] = shutil.get_terminal_size()[0] - 2
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
def format_option_strings(self, option: optparse.Option) -> str:
|
|
||||||
return self._format_option_strings(option)
|
|
||||||
|
|
||||||
def _format_option_strings(
|
|
||||||
self, option: optparse.Option, mvarfmt: str = " <{}>", optsep: str = ", "
|
|
||||||
) -> str:
|
|
||||||
"""
|
|
||||||
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
|
|
||||||
: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():
|
|
||||||
assert option.dest is not None
|
|
||||||
metavar = option.metavar or option.dest.lower()
|
|
||||||
opts.append(mvarfmt.format(metavar.lower()))
|
|
||||||
|
|
||||||
return "".join(opts)
|
|
||||||
|
|
||||||
def format_heading(self, heading: str) -> str:
|
|
||||||
if heading == "Options":
|
|
||||||
return ""
|
|
||||||
return heading + ":\n"
|
|
||||||
|
|
||||||
def format_usage(self, usage: str) -> str:
|
|
||||||
"""
|
|
||||||
Ensure there is only one newline between usage and the first heading
|
|
||||||
if there is no description.
|
|
||||||
"""
|
|
||||||
msg = "\nUsage: {}\n".format(self.indent_lines(textwrap.dedent(usage), " "))
|
|
||||||
return msg
|
|
||||||
|
|
||||||
def format_description(self, description: str) -> str:
|
|
||||||
# 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 = f"{label}:\n{description}\n"
|
|
||||||
return description
|
|
||||||
else:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def format_epilog(self, epilog: str) -> str:
|
|
||||||
# leave full control over epilog to us
|
|
||||||
if epilog:
|
|
||||||
return epilog
|
|
||||||
else:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def indent_lines(self, text: str, indent: str) -> str:
|
|
||||||
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.
|
|
||||||
|
|
||||||
Also redact auth from url type options
|
|
||||||
"""
|
|
||||||
|
|
||||||
def expand_default(self, option: optparse.Option) -> str:
|
|
||||||
default_values = None
|
|
||||||
if self.parser is not None:
|
|
||||||
assert isinstance(self.parser, ConfigOptionParser)
|
|
||||||
self.parser._update_defaults(self.parser.defaults)
|
|
||||||
assert option.dest is not None
|
|
||||||
default_values = self.parser.defaults.get(option.dest)
|
|
||||||
help_text = super().expand_default(option)
|
|
||||||
|
|
||||||
if default_values and option.metavar == "URL":
|
|
||||||
if isinstance(default_values, str):
|
|
||||||
default_values = [default_values]
|
|
||||||
|
|
||||||
# If its not a list, we should abort and just return the help text
|
|
||||||
if not isinstance(default_values, list):
|
|
||||||
default_values = []
|
|
||||||
|
|
||||||
for val in default_values:
|
|
||||||
help_text = help_text.replace(val, redact_auth_from_url(val))
|
|
||||||
|
|
||||||
return help_text
|
|
||||||
|
|
||||||
|
|
||||||
class CustomOptionParser(optparse.OptionParser):
|
|
||||||
def insert_option_group(
|
|
||||||
self, idx: int, *args: Any, **kwargs: Any
|
|
||||||
) -> optparse.OptionGroup:
|
|
||||||
"""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) -> List[optparse.Option]:
|
|
||||||
"""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: Any,
|
|
||||||
name: str,
|
|
||||||
isolated: bool = False,
|
|
||||||
**kwargs: Any,
|
|
||||||
) -> None:
|
|
||||||
self.name = name
|
|
||||||
self.config = Configuration(isolated)
|
|
||||||
|
|
||||||
assert self.name
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
def check_default(self, option: optparse.Option, key: str, val: Any) -> Any:
|
|
||||||
try:
|
|
||||||
return option.check_value(key, val)
|
|
||||||
except optparse.OptionValueError as exc:
|
|
||||||
print(f"An error occurred during configuration: {exc}")
|
|
||||||
sys.exit(3)
|
|
||||||
|
|
||||||
def _get_ordered_configuration_items(self) -> Iterator[Tuple[str, Any]]:
|
|
||||||
# Configuration gives keys in an unordered manner. Order them.
|
|
||||||
override_order = ["global", self.name, ":env:"]
|
|
||||||
|
|
||||||
# Pool the options into different groups
|
|
||||||
section_items: Dict[str, List[Tuple[str, Any]]] = {
|
|
||||||
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: Dict[str, Any]) -> Dict[str, Any]:
|
|
||||||
"""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
|
|
||||||
|
|
||||||
assert option.dest is not None
|
|
||||||
|
|
||||||
if option.action in ("store_true", "store_false"):
|
|
||||||
try:
|
|
||||||
val = strtobool(val)
|
|
||||||
except ValueError:
|
|
||||||
self.error(
|
|
||||||
"{} is not a valid value for {} option, " # noqa
|
|
||||||
"please specify a boolean value like yes/no, "
|
|
||||||
"true/false or 1/0 instead.".format(val, key)
|
|
||||||
)
|
|
||||||
elif option.action == "count":
|
|
||||||
with suppress(ValueError):
|
|
||||||
val = strtobool(val)
|
|
||||||
with suppress(ValueError):
|
|
||||||
val = int(val)
|
|
||||||
if not isinstance(val, int) or val < 0:
|
|
||||||
self.error(
|
|
||||||
"{} is not a valid value for {} option, " # noqa
|
|
||||||
"please instead specify either a non-negative integer "
|
|
||||||
"or a boolean value like yes/no or false/true "
|
|
||||||
"which is equivalent to 1/0.".format(val, key)
|
|
||||||
)
|
|
||||||
elif option.action == "append":
|
|
||||||
val = val.split()
|
|
||||||
val = [self.check_default(option, key, v) for v in val]
|
|
||||||
elif option.action == "callback":
|
|
||||||
assert option.callback is not None
|
|
||||||
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) -> optparse.Values:
|
|
||||||
"""Overriding to make updating the defaults after instantiation of
|
|
||||||
the option parser possible, _update_defaults() does the dirty work."""
|
|
||||||
if not self.process_default_values:
|
|
||||||
# Old, pre-Optik 1.5 behaviour.
|
|
||||||
return optparse.Values(self.defaults)
|
|
||||||
|
|
||||||
# Load the configuration, or error out in case of an error
|
|
||||||
try:
|
|
||||||
self.config.load()
|
|
||||||
except ConfigurationError as err:
|
|
||||||
self.exit(UNKNOWN_ERROR, str(err))
|
|
||||||
|
|
||||||
defaults = self._update_defaults(self.defaults.copy()) # ours
|
|
||||||
for option in self._get_all_options():
|
|
||||||
assert option.dest is not None
|
|
||||||
default = defaults.get(option.dest)
|
|
||||||
if isinstance(default, str):
|
|
||||||
opt_str = option.get_opt_string()
|
|
||||||
defaults[option.dest] = option.check_value(opt_str, default)
|
|
||||||
return optparse.Values(defaults)
|
|
||||||
|
|
||||||
def error(self, msg: str) -> None:
|
|
||||||
self.print_usage(sys.stderr)
|
|
||||||
self.exit(UNKNOWN_ERROR, f"{msg}\n")
|
|
|
@ -1,321 +0,0 @@
|
||||||
import functools
|
|
||||||
import itertools
|
|
||||||
import sys
|
|
||||||
from signal import SIGINT, default_int_handler, signal
|
|
||||||
from typing import Any, Callable, Iterator, Optional, Tuple
|
|
||||||
|
|
||||||
from pip._vendor.progress.bar import Bar, FillingCirclesBar, IncrementalBar
|
|
||||||
from pip._vendor.progress.spinner import Spinner
|
|
||||||
from pip._vendor.rich.progress import (
|
|
||||||
BarColumn,
|
|
||||||
DownloadColumn,
|
|
||||||
FileSizeColumn,
|
|
||||||
Progress,
|
|
||||||
ProgressColumn,
|
|
||||||
SpinnerColumn,
|
|
||||||
TextColumn,
|
|
||||||
TimeElapsedColumn,
|
|
||||||
TimeRemainingColumn,
|
|
||||||
TransferSpeedColumn,
|
|
||||||
)
|
|
||||||
|
|
||||||
from pip._internal.utils.compat import WINDOWS
|
|
||||||
from pip._internal.utils.logging import get_indentation
|
|
||||||
from pip._internal.utils.misc import format_size
|
|
||||||
|
|
||||||
try:
|
|
||||||
from pip._vendor import colorama
|
|
||||||
# Lots of different errors can come from this, including SystemError and
|
|
||||||
# ImportError.
|
|
||||||
except Exception:
|
|
||||||
colorama = None
|
|
||||||
|
|
||||||
DownloadProgressRenderer = Callable[[Iterator[bytes]], Iterator[bytes]]
|
|
||||||
|
|
||||||
|
|
||||||
def _select_progress_class(preferred: Bar, fallback: Bar) -> Bar:
|
|
||||||
encoding = getattr(preferred.file, "encoding", None)
|
|
||||||
|
|
||||||
# If we don't know what encoding this file is in, then we'll just assume
|
|
||||||
# that it doesn't support unicode and use the ASCII bar.
|
|
||||||
if not encoding:
|
|
||||||
return fallback
|
|
||||||
|
|
||||||
# Collect all of the possible characters we want to use with the preferred
|
|
||||||
# bar.
|
|
||||||
characters = [
|
|
||||||
getattr(preferred, "empty_fill", ""),
|
|
||||||
getattr(preferred, "fill", ""),
|
|
||||||
]
|
|
||||||
characters += list(getattr(preferred, "phases", []))
|
|
||||||
|
|
||||||
# Try to decode the characters we're using for the bar using the encoding
|
|
||||||
# of the given file, if this works then we'll assume that we can use the
|
|
||||||
# fancier bar and if not we'll fall back to the plaintext bar.
|
|
||||||
try:
|
|
||||||
"".join(characters).encode(encoding)
|
|
||||||
except UnicodeEncodeError:
|
|
||||||
return fallback
|
|
||||||
else:
|
|
||||||
return preferred
|
|
||||||
|
|
||||||
|
|
||||||
_BaseBar: Any = _select_progress_class(IncrementalBar, Bar)
|
|
||||||
|
|
||||||
|
|
||||||
class InterruptibleMixin:
|
|
||||||
"""
|
|
||||||
Helper to ensure that self.finish() gets called on keyboard interrupt.
|
|
||||||
|
|
||||||
This allows downloads to be interrupted without leaving temporary state
|
|
||||||
(like hidden cursors) behind.
|
|
||||||
|
|
||||||
This class is similar to the progress library's existing SigIntMixin
|
|
||||||
helper, but as of version 1.2, that helper has the following problems:
|
|
||||||
|
|
||||||
1. It calls sys.exit().
|
|
||||||
2. It discards the existing SIGINT handler completely.
|
|
||||||
3. It leaves its own handler in place even after an uninterrupted finish,
|
|
||||||
which will have unexpected delayed effects if the user triggers an
|
|
||||||
unrelated keyboard interrupt some time after a progress-displaying
|
|
||||||
download has already completed, for example.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
||||||
"""
|
|
||||||
Save the original SIGINT handler for later.
|
|
||||||
"""
|
|
||||||
# https://github.com/python/mypy/issues/5887
|
|
||||||
super().__init__(*args, **kwargs) # type: ignore
|
|
||||||
|
|
||||||
self.original_handler = signal(SIGINT, self.handle_sigint)
|
|
||||||
|
|
||||||
# If signal() returns None, the previous handler was not installed from
|
|
||||||
# Python, and we cannot restore it. This probably should not happen,
|
|
||||||
# but if it does, we must restore something sensible instead, at least.
|
|
||||||
# The least bad option should be Python's default SIGINT handler, which
|
|
||||||
# just raises KeyboardInterrupt.
|
|
||||||
if self.original_handler is None:
|
|
||||||
self.original_handler = default_int_handler
|
|
||||||
|
|
||||||
def finish(self) -> None:
|
|
||||||
"""
|
|
||||||
Restore the original SIGINT handler after finishing.
|
|
||||||
|
|
||||||
This should happen regardless of whether the progress display finishes
|
|
||||||
normally, or gets interrupted.
|
|
||||||
"""
|
|
||||||
super().finish() # type: ignore
|
|
||||||
signal(SIGINT, self.original_handler)
|
|
||||||
|
|
||||||
def handle_sigint(self, signum, frame): # type: ignore
|
|
||||||
"""
|
|
||||||
Call self.finish() before delegating to the original SIGINT handler.
|
|
||||||
|
|
||||||
This handler should only be in place while the progress display is
|
|
||||||
active.
|
|
||||||
"""
|
|
||||||
self.finish()
|
|
||||||
self.original_handler(signum, frame)
|
|
||||||
|
|
||||||
|
|
||||||
class SilentBar(Bar):
|
|
||||||
def update(self) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class BlueEmojiBar(IncrementalBar):
|
|
||||||
|
|
||||||
suffix = "%(percent)d%%"
|
|
||||||
bar_prefix = " "
|
|
||||||
bar_suffix = " "
|
|
||||||
phases = ("\U0001F539", "\U0001F537", "\U0001F535")
|
|
||||||
|
|
||||||
|
|
||||||
class DownloadProgressMixin:
|
|
||||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
||||||
# https://github.com/python/mypy/issues/5887
|
|
||||||
super().__init__(*args, **kwargs) # type: ignore
|
|
||||||
self.message: str = (" " * (get_indentation() + 2)) + self.message
|
|
||||||
|
|
||||||
@property
|
|
||||||
def downloaded(self) -> str:
|
|
||||||
return format_size(self.index) # type: ignore
|
|
||||||
|
|
||||||
@property
|
|
||||||
def download_speed(self) -> str:
|
|
||||||
# Avoid zero division errors...
|
|
||||||
if self.avg == 0.0: # type: ignore
|
|
||||||
return "..."
|
|
||||||
return format_size(1 / self.avg) + "/s" # type: ignore
|
|
||||||
|
|
||||||
@property
|
|
||||||
def pretty_eta(self) -> str:
|
|
||||||
if self.eta: # type: ignore
|
|
||||||
return f"eta {self.eta_td}" # type: ignore
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def iter(self, it): # type: ignore
|
|
||||||
for x in it:
|
|
||||||
yield x
|
|
||||||
# B305 is incorrectly raised here
|
|
||||||
# https://github.com/PyCQA/flake8-bugbear/issues/59
|
|
||||||
self.next(len(x)) # noqa: B305
|
|
||||||
self.finish()
|
|
||||||
|
|
||||||
|
|
||||||
class WindowsMixin:
|
|
||||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
||||||
# The Windows terminal does not support the hide/show cursor ANSI codes
|
|
||||||
# even with colorama. So we'll ensure that hide_cursor is False on
|
|
||||||
# Windows.
|
|
||||||
# This call needs to go before the super() call, so that hide_cursor
|
|
||||||
# is set in time. The base progress bar class writes the "hide cursor"
|
|
||||||
# code to the terminal in its init, so if we don't set this soon
|
|
||||||
# enough, we get a "hide" with no corresponding "show"...
|
|
||||||
if WINDOWS and self.hide_cursor: # type: ignore
|
|
||||||
self.hide_cursor = False
|
|
||||||
|
|
||||||
# https://github.com/python/mypy/issues/5887
|
|
||||||
super().__init__(*args, **kwargs) # type: ignore
|
|
||||||
|
|
||||||
# Check if we are running on Windows and we have the colorama module,
|
|
||||||
# if we do then wrap our file with it.
|
|
||||||
if WINDOWS and colorama:
|
|
||||||
self.file = colorama.AnsiToWin32(self.file) # type: ignore
|
|
||||||
# The progress code expects to be able to call self.file.isatty()
|
|
||||||
# but the colorama.AnsiToWin32() object doesn't have that, so we'll
|
|
||||||
# add it.
|
|
||||||
self.file.isatty = lambda: self.file.wrapped.isatty()
|
|
||||||
# The progress code expects to be able to call self.file.flush()
|
|
||||||
# but the colorama.AnsiToWin32() object doesn't have that, so we'll
|
|
||||||
# add it.
|
|
||||||
self.file.flush = lambda: self.file.wrapped.flush()
|
|
||||||
|
|
||||||
|
|
||||||
class BaseDownloadProgressBar(WindowsMixin, InterruptibleMixin, DownloadProgressMixin):
|
|
||||||
|
|
||||||
file = sys.stdout
|
|
||||||
message = "%(percent)d%%"
|
|
||||||
suffix = "%(downloaded)s %(download_speed)s %(pretty_eta)s"
|
|
||||||
|
|
||||||
|
|
||||||
class DefaultDownloadProgressBar(BaseDownloadProgressBar, _BaseBar):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class DownloadSilentBar(BaseDownloadProgressBar, SilentBar):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class DownloadBar(BaseDownloadProgressBar, Bar):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class DownloadFillingCirclesBar(BaseDownloadProgressBar, FillingCirclesBar):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class DownloadBlueEmojiProgressBar(BaseDownloadProgressBar, BlueEmojiBar):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class DownloadProgressSpinner(
|
|
||||||
WindowsMixin, InterruptibleMixin, DownloadProgressMixin, Spinner
|
|
||||||
):
|
|
||||||
|
|
||||||
file = sys.stdout
|
|
||||||
suffix = "%(downloaded)s %(download_speed)s"
|
|
||||||
|
|
||||||
def next_phase(self) -> str:
|
|
||||||
if not hasattr(self, "_phaser"):
|
|
||||||
self._phaser = itertools.cycle(self.phases)
|
|
||||||
return next(self._phaser)
|
|
||||||
|
|
||||||
def update(self) -> None:
|
|
||||||
message = self.message % self
|
|
||||||
phase = self.next_phase()
|
|
||||||
suffix = self.suffix % self
|
|
||||||
line = "".join(
|
|
||||||
[
|
|
||||||
message,
|
|
||||||
" " if message else "",
|
|
||||||
phase,
|
|
||||||
" " if suffix else "",
|
|
||||||
suffix,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
self.writeln(line)
|
|
||||||
|
|
||||||
|
|
||||||
BAR_TYPES = {
|
|
||||||
"off": (DownloadSilentBar, DownloadSilentBar),
|
|
||||||
"on": (DefaultDownloadProgressBar, DownloadProgressSpinner),
|
|
||||||
"ascii": (DownloadBar, DownloadProgressSpinner),
|
|
||||||
"pretty": (DownloadFillingCirclesBar, DownloadProgressSpinner),
|
|
||||||
"emoji": (DownloadBlueEmojiProgressBar, DownloadProgressSpinner),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def _legacy_progress_bar(
|
|
||||||
progress_bar: str, max: Optional[int]
|
|
||||||
) -> DownloadProgressRenderer:
|
|
||||||
if max is None or max == 0:
|
|
||||||
return BAR_TYPES[progress_bar][1]().iter # type: ignore
|
|
||||||
else:
|
|
||||||
return BAR_TYPES[progress_bar][0](max=max).iter
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# Modern replacement, for our legacy progress bars.
|
|
||||||
#
|
|
||||||
def _rich_progress_bar(
|
|
||||||
iterable: Iterator[bytes],
|
|
||||||
*,
|
|
||||||
bar_type: str,
|
|
||||||
size: int,
|
|
||||||
) -> Iterator[bytes]:
|
|
||||||
assert bar_type == "on", "This should only be used in the default mode."
|
|
||||||
|
|
||||||
if not size:
|
|
||||||
total = float("inf")
|
|
||||||
columns: Tuple[ProgressColumn, ...] = (
|
|
||||||
TextColumn("[progress.description]{task.description}"),
|
|
||||||
SpinnerColumn("line", speed=1.5),
|
|
||||||
FileSizeColumn(),
|
|
||||||
TransferSpeedColumn(),
|
|
||||||
TimeElapsedColumn(),
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
total = size
|
|
||||||
columns = (
|
|
||||||
TextColumn("[progress.description]{task.description}"),
|
|
||||||
BarColumn(),
|
|
||||||
DownloadColumn(),
|
|
||||||
TransferSpeedColumn(),
|
|
||||||
TextColumn("eta"),
|
|
||||||
TimeRemainingColumn(),
|
|
||||||
)
|
|
||||||
|
|
||||||
progress = Progress(*columns, refresh_per_second=30)
|
|
||||||
task_id = progress.add_task(" " * (get_indentation() + 2), total=total)
|
|
||||||
with progress:
|
|
||||||
for chunk in iterable:
|
|
||||||
yield chunk
|
|
||||||
progress.update(task_id, advance=len(chunk))
|
|
||||||
|
|
||||||
|
|
||||||
def get_download_progress_renderer(
|
|
||||||
*, bar_type: str, size: Optional[int] = None
|
|
||||||
) -> DownloadProgressRenderer:
|
|
||||||
"""Get an object that can be used to render the download progress.
|
|
||||||
|
|
||||||
Returns a callable, that takes an iterable to "wrap".
|
|
||||||
"""
|
|
||||||
if bar_type == "on":
|
|
||||||
return functools.partial(_rich_progress_bar, bar_type=bar_type, size=size)
|
|
||||||
elif bar_type == "off":
|
|
||||||
return iter # no-op, when passed an iterator
|
|
||||||
else:
|
|
||||||
return _legacy_progress_bar(bar_type, size)
|
|
|
@ -1,506 +0,0 @@
|
||||||
"""Contains the Command base classes that depend on PipSession.
|
|
||||||
|
|
||||||
The classes in this module are in a separate module so the commands not
|
|
||||||
needing download / PackageFinder capability don't unnecessarily import the
|
|
||||||
PackageFinder machinery and all its vendored dependencies, etc.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
from functools import partial
|
|
||||||
from optparse import Values
|
|
||||||
from typing import Any, List, Optional, Tuple
|
|
||||||
|
|
||||||
from pip._internal.cache import WheelCache
|
|
||||||
from pip._internal.cli import cmdoptions
|
|
||||||
from pip._internal.cli.base_command import Command
|
|
||||||
from pip._internal.cli.command_context import CommandContextMixIn
|
|
||||||
from pip._internal.exceptions import CommandError, PreviousBuildDirError
|
|
||||||
from pip._internal.index.collector import LinkCollector
|
|
||||||
from pip._internal.index.package_finder import PackageFinder
|
|
||||||
from pip._internal.models.selection_prefs import SelectionPreferences
|
|
||||||
from pip._internal.models.target_python import TargetPython
|
|
||||||
from pip._internal.network.session import PipSession
|
|
||||||
from pip._internal.operations.prepare import RequirementPreparer
|
|
||||||
from pip._internal.req.constructors import (
|
|
||||||
install_req_from_editable,
|
|
||||||
install_req_from_line,
|
|
||||||
install_req_from_parsed_requirement,
|
|
||||||
install_req_from_req_string,
|
|
||||||
)
|
|
||||||
from pip._internal.req.req_file import parse_requirements
|
|
||||||
from pip._internal.req.req_install import InstallRequirement
|
|
||||||
from pip._internal.req.req_tracker import RequirementTracker
|
|
||||||
from pip._internal.resolution.base import BaseResolver
|
|
||||||
from pip._internal.self_outdated_check import pip_self_version_check
|
|
||||||
from pip._internal.utils.deprecation import deprecated
|
|
||||||
from pip._internal.utils.temp_dir import (
|
|
||||||
TempDirectory,
|
|
||||||
TempDirectoryTypeRegistry,
|
|
||||||
tempdir_kinds,
|
|
||||||
)
|
|
||||||
from pip._internal.utils.virtualenv import running_under_virtualenv
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class SessionCommandMixin(CommandContextMixIn):
|
|
||||||
|
|
||||||
"""
|
|
||||||
A class mixin for command classes needing _build_session().
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
super().__init__()
|
|
||||||
self._session: Optional[PipSession] = None
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _get_index_urls(cls, options: Values) -> Optional[List[str]]:
|
|
||||||
"""Return a list of index urls from user-provided options."""
|
|
||||||
index_urls = []
|
|
||||||
if not getattr(options, "no_index", False):
|
|
||||||
url = getattr(options, "index_url", None)
|
|
||||||
if url:
|
|
||||||
index_urls.append(url)
|
|
||||||
urls = getattr(options, "extra_index_urls", None)
|
|
||||||
if urls:
|
|
||||||
index_urls.extend(urls)
|
|
||||||
# Return None rather than an empty list
|
|
||||||
return index_urls or None
|
|
||||||
|
|
||||||
def get_default_session(self, options: Values) -> PipSession:
|
|
||||||
"""Get a default-managed session."""
|
|
||||||
if self._session is None:
|
|
||||||
self._session = self.enter_context(self._build_session(options))
|
|
||||||
# there's no type annotation on requests.Session, so it's
|
|
||||||
# automatically ContextManager[Any] and self._session becomes Any,
|
|
||||||
# then https://github.com/python/mypy/issues/7696 kicks in
|
|
||||||
assert self._session is not None
|
|
||||||
return self._session
|
|
||||||
|
|
||||||
def _build_session(
|
|
||||||
self,
|
|
||||||
options: Values,
|
|
||||||
retries: Optional[int] = None,
|
|
||||||
timeout: Optional[int] = None,
|
|
||||||
) -> PipSession:
|
|
||||||
assert not options.cache_dir or os.path.isabs(options.cache_dir)
|
|
||||||
session = PipSession(
|
|
||||||
cache=(
|
|
||||||
os.path.join(options.cache_dir, "http") if options.cache_dir else None
|
|
||||||
),
|
|
||||||
retries=retries if retries is not None else options.retries,
|
|
||||||
trusted_hosts=options.trusted_hosts,
|
|
||||||
index_urls=self._get_index_urls(options),
|
|
||||||
)
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
|
|
||||||
class IndexGroupCommand(Command, SessionCommandMixin):
|
|
||||||
|
|
||||||
"""
|
|
||||||
Abstract base class for commands with the index_group options.
|
|
||||||
|
|
||||||
This also corresponds to the commands that permit the pip version check.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def handle_pip_version_check(self, options: Values) -> None:
|
|
||||||
"""
|
|
||||||
Do the pip version check if not disabled.
|
|
||||||
|
|
||||||
This overrides the default behavior of not doing the check.
|
|
||||||
"""
|
|
||||||
# Make sure the index_group options are present.
|
|
||||||
assert hasattr(options, "no_index")
|
|
||||||
|
|
||||||
if options.disable_pip_version_check or options.no_index:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Otherwise, check if we're using the latest version of pip available.
|
|
||||||
session = self._build_session(
|
|
||||||
options, retries=0, timeout=min(5, options.timeout)
|
|
||||||
)
|
|
||||||
with session:
|
|
||||||
pip_self_version_check(session, options)
|
|
||||||
|
|
||||||
|
|
||||||
KEEPABLE_TEMPDIR_TYPES = [
|
|
||||||
tempdir_kinds.BUILD_ENV,
|
|
||||||
tempdir_kinds.EPHEM_WHEEL_CACHE,
|
|
||||||
tempdir_kinds.REQ_BUILD,
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def warn_if_run_as_root() -> None:
|
|
||||||
"""Output a warning for sudo users on Unix.
|
|
||||||
|
|
||||||
In a virtual environment, sudo pip still writes to virtualenv.
|
|
||||||
On Windows, users may run pip as Administrator without issues.
|
|
||||||
This warning only applies to Unix root users outside of virtualenv.
|
|
||||||
"""
|
|
||||||
if running_under_virtualenv():
|
|
||||||
return
|
|
||||||
if not hasattr(os, "getuid"):
|
|
||||||
return
|
|
||||||
# On Windows, there are no "system managed" Python packages. Installing as
|
|
||||||
# Administrator via pip is the correct way of updating system environments.
|
|
||||||
#
|
|
||||||
# We choose sys.platform over utils.compat.WINDOWS here to enable Mypy platform
|
|
||||||
# checks: https://mypy.readthedocs.io/en/stable/common_issues.html
|
|
||||||
if sys.platform == "win32" or sys.platform == "cygwin":
|
|
||||||
return
|
|
||||||
|
|
||||||
if os.getuid() != 0:
|
|
||||||
return
|
|
||||||
|
|
||||||
logger.warning(
|
|
||||||
"Running pip as the 'root' user can result in broken permissions and "
|
|
||||||
"conflicting behaviour with the system package manager. "
|
|
||||||
"It is recommended to use a virtual environment instead: "
|
|
||||||
"https://pip.pypa.io/warnings/venv"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def with_cleanup(func: Any) -> Any:
|
|
||||||
"""Decorator for common logic related to managing temporary
|
|
||||||
directories.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def configure_tempdir_registry(registry: TempDirectoryTypeRegistry) -> None:
|
|
||||||
for t in KEEPABLE_TEMPDIR_TYPES:
|
|
||||||
registry.set_delete(t, False)
|
|
||||||
|
|
||||||
def wrapper(
|
|
||||||
self: RequirementCommand, options: Values, args: List[Any]
|
|
||||||
) -> Optional[int]:
|
|
||||||
assert self.tempdir_registry is not None
|
|
||||||
if options.no_clean:
|
|
||||||
configure_tempdir_registry(self.tempdir_registry)
|
|
||||||
|
|
||||||
try:
|
|
||||||
return func(self, options, args)
|
|
||||||
except PreviousBuildDirError:
|
|
||||||
# This kind of conflict can occur when the user passes an explicit
|
|
||||||
# build directory with a pre-existing folder. In that case we do
|
|
||||||
# not want to accidentally remove it.
|
|
||||||
configure_tempdir_registry(self.tempdir_registry)
|
|
||||||
raise
|
|
||||||
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
|
|
||||||
class RequirementCommand(IndexGroupCommand):
|
|
||||||
def __init__(self, *args: Any, **kw: Any) -> None:
|
|
||||||
super().__init__(*args, **kw)
|
|
||||||
|
|
||||||
self.cmd_opts.add_option(cmdoptions.no_clean())
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def determine_resolver_variant(options: Values) -> str:
|
|
||||||
"""Determines which resolver should be used, based on the given options."""
|
|
||||||
if "legacy-resolver" in options.deprecated_features_enabled:
|
|
||||||
return "legacy"
|
|
||||||
|
|
||||||
return "2020-resolver"
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def determine_build_failure_suppression(options: Values) -> bool:
|
|
||||||
"""Determines whether build failures should be suppressed and backtracked on."""
|
|
||||||
if "backtrack-on-build-failures" not in options.deprecated_features_enabled:
|
|
||||||
return False
|
|
||||||
|
|
||||||
if "legacy-resolver" in options.deprecated_features_enabled:
|
|
||||||
raise CommandError("Cannot backtrack with legacy resolver.")
|
|
||||||
|
|
||||||
deprecated(
|
|
||||||
reason=(
|
|
||||||
"Backtracking on build failures can mask issues related to how "
|
|
||||||
"a package generates metadata or builds a wheel. This flag will "
|
|
||||||
"be removed in pip 22.2."
|
|
||||||
),
|
|
||||||
gone_in=None,
|
|
||||||
replacement=(
|
|
||||||
"avoiding known-bad versions by explicitly telling pip to ignore them "
|
|
||||||
"(either directly as requirements, or via a constraints file)"
|
|
||||||
),
|
|
||||||
feature_flag=None,
|
|
||||||
issue=10655,
|
|
||||||
)
|
|
||||||
return True
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def make_requirement_preparer(
|
|
||||||
cls,
|
|
||||||
temp_build_dir: TempDirectory,
|
|
||||||
options: Values,
|
|
||||||
req_tracker: RequirementTracker,
|
|
||||||
session: PipSession,
|
|
||||||
finder: PackageFinder,
|
|
||||||
use_user_site: bool,
|
|
||||||
download_dir: Optional[str] = None,
|
|
||||||
verbosity: int = 0,
|
|
||||||
) -> RequirementPreparer:
|
|
||||||
"""
|
|
||||||
Create a RequirementPreparer instance for the given parameters.
|
|
||||||
"""
|
|
||||||
temp_build_dir_path = temp_build_dir.path
|
|
||||||
assert temp_build_dir_path is not None
|
|
||||||
|
|
||||||
resolver_variant = cls.determine_resolver_variant(options)
|
|
||||||
if resolver_variant == "2020-resolver":
|
|
||||||
lazy_wheel = "fast-deps" in options.features_enabled
|
|
||||||
if lazy_wheel:
|
|
||||||
logger.warning(
|
|
||||||
"pip is using lazily downloaded wheels using HTTP "
|
|
||||||
"range requests to obtain dependency information. "
|
|
||||||
"This experimental feature is enabled through "
|
|
||||||
"--use-feature=fast-deps and it is not ready for "
|
|
||||||
"production."
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
lazy_wheel = False
|
|
||||||
if "fast-deps" in options.features_enabled:
|
|
||||||
logger.warning(
|
|
||||||
"fast-deps has no effect when used with the legacy resolver."
|
|
||||||
)
|
|
||||||
|
|
||||||
in_tree_build = "out-of-tree-build" not in options.deprecated_features_enabled
|
|
||||||
if "in-tree-build" in options.features_enabled:
|
|
||||||
deprecated(
|
|
||||||
reason="In-tree builds are now the default.",
|
|
||||||
replacement="to remove the --use-feature=in-tree-build flag",
|
|
||||||
gone_in="22.1",
|
|
||||||
)
|
|
||||||
if "out-of-tree-build" in options.deprecated_features_enabled:
|
|
||||||
deprecated(
|
|
||||||
reason="Out-of-tree builds are deprecated.",
|
|
||||||
replacement=None,
|
|
||||||
gone_in="22.1",
|
|
||||||
)
|
|
||||||
|
|
||||||
if options.progress_bar not in {"on", "off"}:
|
|
||||||
deprecated(
|
|
||||||
reason="Custom progress bar styles are deprecated",
|
|
||||||
replacement="to use the default progress bar style.",
|
|
||||||
gone_in="22.1",
|
|
||||||
)
|
|
||||||
|
|
||||||
return RequirementPreparer(
|
|
||||||
build_dir=temp_build_dir_path,
|
|
||||||
src_dir=options.src_dir,
|
|
||||||
download_dir=download_dir,
|
|
||||||
build_isolation=options.build_isolation,
|
|
||||||
req_tracker=req_tracker,
|
|
||||||
session=session,
|
|
||||||
progress_bar=options.progress_bar,
|
|
||||||
finder=finder,
|
|
||||||
require_hashes=options.require_hashes,
|
|
||||||
use_user_site=use_user_site,
|
|
||||||
lazy_wheel=lazy_wheel,
|
|
||||||
verbosity=verbosity,
|
|
||||||
in_tree_build=in_tree_build,
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def make_resolver(
|
|
||||||
cls,
|
|
||||||
preparer: RequirementPreparer,
|
|
||||||
finder: PackageFinder,
|
|
||||||
options: Values,
|
|
||||||
wheel_cache: Optional[WheelCache] = None,
|
|
||||||
use_user_site: bool = False,
|
|
||||||
ignore_installed: bool = True,
|
|
||||||
ignore_requires_python: bool = False,
|
|
||||||
force_reinstall: bool = False,
|
|
||||||
upgrade_strategy: str = "to-satisfy-only",
|
|
||||||
use_pep517: Optional[bool] = None,
|
|
||||||
py_version_info: Optional[Tuple[int, ...]] = None,
|
|
||||||
) -> BaseResolver:
|
|
||||||
"""
|
|
||||||
Create a Resolver instance for the given parameters.
|
|
||||||
"""
|
|
||||||
make_install_req = partial(
|
|
||||||
install_req_from_req_string,
|
|
||||||
isolated=options.isolated_mode,
|
|
||||||
use_pep517=use_pep517,
|
|
||||||
)
|
|
||||||
suppress_build_failures = cls.determine_build_failure_suppression(options)
|
|
||||||
resolver_variant = cls.determine_resolver_variant(options)
|
|
||||||
# The long import name and duplicated invocation is needed to convince
|
|
||||||
# Mypy into correctly typechecking. Otherwise it would complain the
|
|
||||||
# "Resolver" class being redefined.
|
|
||||||
if resolver_variant == "2020-resolver":
|
|
||||||
import pip._internal.resolution.resolvelib.resolver
|
|
||||||
|
|
||||||
return pip._internal.resolution.resolvelib.resolver.Resolver(
|
|
||||||
preparer=preparer,
|
|
||||||
finder=finder,
|
|
||||||
wheel_cache=wheel_cache,
|
|
||||||
make_install_req=make_install_req,
|
|
||||||
use_user_site=use_user_site,
|
|
||||||
ignore_dependencies=options.ignore_dependencies,
|
|
||||||
ignore_installed=ignore_installed,
|
|
||||||
ignore_requires_python=ignore_requires_python,
|
|
||||||
force_reinstall=force_reinstall,
|
|
||||||
upgrade_strategy=upgrade_strategy,
|
|
||||||
py_version_info=py_version_info,
|
|
||||||
suppress_build_failures=suppress_build_failures,
|
|
||||||
)
|
|
||||||
import pip._internal.resolution.legacy.resolver
|
|
||||||
|
|
||||||
return pip._internal.resolution.legacy.resolver.Resolver(
|
|
||||||
preparer=preparer,
|
|
||||||
finder=finder,
|
|
||||||
wheel_cache=wheel_cache,
|
|
||||||
make_install_req=make_install_req,
|
|
||||||
use_user_site=use_user_site,
|
|
||||||
ignore_dependencies=options.ignore_dependencies,
|
|
||||||
ignore_installed=ignore_installed,
|
|
||||||
ignore_requires_python=ignore_requires_python,
|
|
||||||
force_reinstall=force_reinstall,
|
|
||||||
upgrade_strategy=upgrade_strategy,
|
|
||||||
py_version_info=py_version_info,
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_requirements(
|
|
||||||
self,
|
|
||||||
args: List[str],
|
|
||||||
options: Values,
|
|
||||||
finder: PackageFinder,
|
|
||||||
session: PipSession,
|
|
||||||
) -> List[InstallRequirement]:
|
|
||||||
"""
|
|
||||||
Parse command-line arguments into the corresponding requirements.
|
|
||||||
"""
|
|
||||||
requirements: List[InstallRequirement] = []
|
|
||||||
for filename in options.constraints:
|
|
||||||
for parsed_req in parse_requirements(
|
|
||||||
filename,
|
|
||||||
constraint=True,
|
|
||||||
finder=finder,
|
|
||||||
options=options,
|
|
||||||
session=session,
|
|
||||||
):
|
|
||||||
req_to_add = install_req_from_parsed_requirement(
|
|
||||||
parsed_req,
|
|
||||||
isolated=options.isolated_mode,
|
|
||||||
user_supplied=False,
|
|
||||||
)
|
|
||||||
requirements.append(req_to_add)
|
|
||||||
|
|
||||||
for req in args:
|
|
||||||
req_to_add = install_req_from_line(
|
|
||||||
req,
|
|
||||||
None,
|
|
||||||
isolated=options.isolated_mode,
|
|
||||||
use_pep517=options.use_pep517,
|
|
||||||
user_supplied=True,
|
|
||||||
)
|
|
||||||
requirements.append(req_to_add)
|
|
||||||
|
|
||||||
for req in options.editables:
|
|
||||||
req_to_add = install_req_from_editable(
|
|
||||||
req,
|
|
||||||
user_supplied=True,
|
|
||||||
isolated=options.isolated_mode,
|
|
||||||
use_pep517=options.use_pep517,
|
|
||||||
)
|
|
||||||
requirements.append(req_to_add)
|
|
||||||
|
|
||||||
# NOTE: options.require_hashes may be set if --require-hashes is True
|
|
||||||
for filename in options.requirements:
|
|
||||||
for parsed_req in parse_requirements(
|
|
||||||
filename, finder=finder, options=options, session=session
|
|
||||||
):
|
|
||||||
req_to_add = install_req_from_parsed_requirement(
|
|
||||||
parsed_req,
|
|
||||||
isolated=options.isolated_mode,
|
|
||||||
use_pep517=options.use_pep517,
|
|
||||||
user_supplied=True,
|
|
||||||
)
|
|
||||||
requirements.append(req_to_add)
|
|
||||||
|
|
||||||
# If any requirement has hash options, enable hash checking.
|
|
||||||
if any(req.has_hash_options for req in requirements):
|
|
||||||
options.require_hashes = True
|
|
||||||
|
|
||||||
if not (args or options.editables or options.requirements):
|
|
||||||
opts = {"name": self.name}
|
|
||||||
if options.find_links:
|
|
||||||
raise CommandError(
|
|
||||||
"You must give at least one requirement to {name} "
|
|
||||||
'(maybe you meant "pip {name} {links}"?)'.format(
|
|
||||||
**dict(opts, links=" ".join(options.find_links))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
raise CommandError(
|
|
||||||
"You must give at least one requirement to {name} "
|
|
||||||
'(see "pip help {name}")'.format(**opts)
|
|
||||||
)
|
|
||||||
|
|
||||||
return requirements
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def trace_basic_info(finder: PackageFinder) -> None:
|
|
||||||
"""
|
|
||||||
Trace basic information about the provided objects.
|
|
||||||
"""
|
|
||||||
# Display where finder is looking for packages
|
|
||||||
search_scope = finder.search_scope
|
|
||||||
locations = search_scope.get_formatted_locations()
|
|
||||||
if locations:
|
|
||||||
logger.info(locations)
|
|
||||||
|
|
||||||
def _build_package_finder(
|
|
||||||
self,
|
|
||||||
options: Values,
|
|
||||||
session: PipSession,
|
|
||||||
target_python: Optional[TargetPython] = None,
|
|
||||||
ignore_requires_python: Optional[bool] = None,
|
|
||||||
) -> PackageFinder:
|
|
||||||
"""
|
|
||||||
Create a package finder appropriate to this requirement command.
|
|
||||||
|
|
||||||
:param ignore_requires_python: Whether to ignore incompatible
|
|
||||||
"Requires-Python" values in links. Defaults to False.
|
|
||||||
"""
|
|
||||||
link_collector = LinkCollector.create(session, options=options)
|
|
||||||
selection_prefs = SelectionPreferences(
|
|
||||||
allow_yanked=True,
|
|
||||||
format_control=options.format_control,
|
|
||||||
allow_all_prereleases=options.pre,
|
|
||||||
prefer_binary=options.prefer_binary,
|
|
||||||
ignore_requires_python=ignore_requires_python,
|
|
||||||
)
|
|
||||||
|
|
||||||
return PackageFinder.create(
|
|
||||||
link_collector=link_collector,
|
|
||||||
selection_prefs=selection_prefs,
|
|
||||||
target_python=target_python,
|
|
||||||
use_deprecated_html5lib="html5lib" in options.deprecated_features_enabled,
|
|
||||||
)
|
|
|
@ -1,157 +0,0 @@
|
||||||
import contextlib
|
|
||||||
import itertools
|
|
||||||
import logging
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
from typing import IO, Iterator
|
|
||||||
|
|
||||||
from pip._vendor.progress import HIDE_CURSOR, SHOW_CURSOR
|
|
||||||
|
|
||||||
from pip._internal.utils.compat import WINDOWS
|
|
||||||
from pip._internal.utils.logging import get_indentation
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class SpinnerInterface:
|
|
||||||
def spin(self) -> None:
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
def finish(self, final_status: str) -> None:
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
|
|
||||||
class InteractiveSpinner(SpinnerInterface):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
message: str,
|
|
||||||
file: IO[str] = None,
|
|
||||||
spin_chars: str = "-\\|/",
|
|
||||||
# Empirically, 8 updates/second looks nice
|
|
||||||
min_update_interval_seconds: float = 0.125,
|
|
||||||
):
|
|
||||||
self._message = message
|
|
||||||
if file is None:
|
|
||||||
file = sys.stdout
|
|
||||||
self._file = file
|
|
||||||
self._rate_limiter = RateLimiter(min_update_interval_seconds)
|
|
||||||
self._finished = False
|
|
||||||
|
|
||||||
self._spin_cycle = itertools.cycle(spin_chars)
|
|
||||||
|
|
||||||
self._file.write(" " * get_indentation() + self._message + " ... ")
|
|
||||||
self._width = 0
|
|
||||||
|
|
||||||
def _write(self, status: str) -> None:
|
|
||||||
assert not self._finished
|
|
||||||
# Erase what we wrote before by backspacing to the beginning, writing
|
|
||||||
# spaces to overwrite the old text, and then backspacing again
|
|
||||||
backup = "\b" * self._width
|
|
||||||
self._file.write(backup + " " * self._width + backup)
|
|
||||||
# Now we have a blank slate to add our status
|
|
||||||
self._file.write(status)
|
|
||||||
self._width = len(status)
|
|
||||||
self._file.flush()
|
|
||||||
self._rate_limiter.reset()
|
|
||||||
|
|
||||||
def spin(self) -> None:
|
|
||||||
if self._finished:
|
|
||||||
return
|
|
||||||
if not self._rate_limiter.ready():
|
|
||||||
return
|
|
||||||
self._write(next(self._spin_cycle))
|
|
||||||
|
|
||||||
def finish(self, final_status: str) -> None:
|
|
||||||
if self._finished:
|
|
||||||
return
|
|
||||||
self._write(final_status)
|
|
||||||
self._file.write("\n")
|
|
||||||
self._file.flush()
|
|
||||||
self._finished = True
|
|
||||||
|
|
||||||
|
|
||||||
# Used for dumb terminals, non-interactive installs (no tty), etc.
|
|
||||||
# We still print updates occasionally (once every 60 seconds by default) to
|
|
||||||
# act as a keep-alive for systems like Travis-CI that take lack-of-output as
|
|
||||||
# an indication that a task has frozen.
|
|
||||||
class NonInteractiveSpinner(SpinnerInterface):
|
|
||||||
def __init__(self, message: str, min_update_interval_seconds: float = 60.0) -> None:
|
|
||||||
self._message = message
|
|
||||||
self._finished = False
|
|
||||||
self._rate_limiter = RateLimiter(min_update_interval_seconds)
|
|
||||||
self._update("started")
|
|
||||||
|
|
||||||
def _update(self, status: str) -> None:
|
|
||||||
assert not self._finished
|
|
||||||
self._rate_limiter.reset()
|
|
||||||
logger.info("%s: %s", self._message, status)
|
|
||||||
|
|
||||||
def spin(self) -> None:
|
|
||||||
if self._finished:
|
|
||||||
return
|
|
||||||
if not self._rate_limiter.ready():
|
|
||||||
return
|
|
||||||
self._update("still running...")
|
|
||||||
|
|
||||||
def finish(self, final_status: str) -> None:
|
|
||||||
if self._finished:
|
|
||||||
return
|
|
||||||
self._update(f"finished with status '{final_status}'")
|
|
||||||
self._finished = True
|
|
||||||
|
|
||||||
|
|
||||||
class RateLimiter:
|
|
||||||
def __init__(self, min_update_interval_seconds: float) -> None:
|
|
||||||
self._min_update_interval_seconds = min_update_interval_seconds
|
|
||||||
self._last_update: float = 0
|
|
||||||
|
|
||||||
def ready(self) -> bool:
|
|
||||||
now = time.time()
|
|
||||||
delta = now - self._last_update
|
|
||||||
return delta >= self._min_update_interval_seconds
|
|
||||||
|
|
||||||
def reset(self) -> None:
|
|
||||||
self._last_update = time.time()
|
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def open_spinner(message: str) -> Iterator[SpinnerInterface]:
|
|
||||||
# Interactive spinner goes directly to sys.stdout rather than being routed
|
|
||||||
# through the logging system, but it acts like it has level INFO,
|
|
||||||
# i.e. it's only displayed if we're at level INFO or better.
|
|
||||||
# Non-interactive spinner goes through the logging system, so it is always
|
|
||||||
# in sync with logging configuration.
|
|
||||||
if sys.stdout.isatty() and logger.getEffectiveLevel() <= logging.INFO:
|
|
||||||
spinner: SpinnerInterface = InteractiveSpinner(message)
|
|
||||||
else:
|
|
||||||
spinner = NonInteractiveSpinner(message)
|
|
||||||
try:
|
|
||||||
with hidden_cursor(sys.stdout):
|
|
||||||
yield spinner
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
spinner.finish("canceled")
|
|
||||||
raise
|
|
||||||
except Exception:
|
|
||||||
spinner.finish("error")
|
|
||||||
raise
|
|
||||||
else:
|
|
||||||
spinner.finish("done")
|
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def hidden_cursor(file: IO[str]) -> Iterator[None]:
|
|
||||||
# The Windows terminal does not support the hide/show cursor ANSI codes,
|
|
||||||
# even via colorama. So don't even try.
|
|
||||||
if WINDOWS:
|
|
||||||
yield
|
|
||||||
# We don't want to clutter the output with control characters if we're
|
|
||||||
# writing to a file, or if the user is running with --quiet.
|
|
||||||
# See https://github.com/pypa/pip/issues/3418
|
|
||||||
elif not file.isatty() or logger.getEffectiveLevel() > logging.INFO:
|
|
||||||
yield
|
|
||||||
else:
|
|
||||||
file.write(HIDE_CURSOR)
|
|
||||||
try:
|
|
||||||
yield
|
|
||||||
finally:
|
|
||||||
file.write(SHOW_CURSOR)
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue