remove venv
@@ -56,7 +56,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="logo">
|
<div class="logo">
|
||||||
<a href="https://github.com/timothypidashev" target="_blank">
|
<a href="https://github.com/timmypidashev.com" target="_blank">
|
||||||
<img src="static/images/elements/png/github.png" alt="github logo" data-aos="zoom-out" class="logo__image">
|
<img src="static/images/elements/png/github.png" alt="github logo" data-aos="zoom-out" class="logo__image">
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 653 B After Width: | Height: | Size: 653 B |
|
Before Width: | Height: | Size: 630 B After Width: | Height: | Size: 630 B |
|
Before Width: | Height: | Size: 515 B After Width: | Height: | Size: 515 B |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
@@ -1,11 +0,0 @@
|
|||||||
> Why do I have a folder named ".vercel" in my project?
|
|
||||||
The ".vercel" folder is created when you link a directory to a Vercel project.
|
|
||||||
|
|
||||||
> What does the "project.json" file contain?
|
|
||||||
The "project.json" file contains:
|
|
||||||
- The ID of the Vercel project that you linked ("projectId")
|
|
||||||
- The ID of the user or team your Vercel project is owned by ("orgId")
|
|
||||||
|
|
||||||
> Should I commit the ".vercel" folder?
|
|
||||||
No, you should not share the ".vercel" folder with anyone.
|
|
||||||
Upon creation, it will be automatically added to your ".gitignore" file.
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"projectId":"prj_fhKjryWYHXc2me4RvhUk0RTMRgSR","orgId":"RXDy9fsHHUMdP3Y9CCzxaa7w"}
|
|
||||||
@@ -1,241 +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 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 virutal 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# 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,66 +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
|
|
||||||
if [ ! "${1:-}" = "nondestructive" ] ; then
|
|
||||||
# Self destruct!
|
|
||||||
unset -f deactivate
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# unset irrelevant variables
|
|
||||||
deactivate nondestructive
|
|
||||||
|
|
||||||
VIRTUAL_ENV="/home/timothypidashev/Desktop/Github/Portfolio/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
|
|
||||||
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,25 +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; test "\!:*" != "nondestructive" && unalias deactivate'
|
|
||||||
|
|
||||||
# Unset irrelevant variables.
|
|
||||||
deactivate nondestructive
|
|
||||||
|
|
||||||
setenv VIRTUAL_ENV "/home/timothypidashev/Desktop/Github/Portfolio/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"
|
|
||||||
endif
|
|
||||||
|
|
||||||
alias pydoc python -m pydoc
|
|
||||||
|
|
||||||
rehash
|
|
||||||
@@ -1,64 +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
|
|
||||||
if test "$argv[1]" != "nondestructive"
|
|
||||||
# Self-destruct!
|
|
||||||
functions -e deactivate
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Unset irrelevant variables.
|
|
||||||
deactivate nondestructive
|
|
||||||
|
|
||||||
set -gx VIRTUAL_ENV "/home/timothypidashev/Desktop/Github/Portfolio/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"
|
|
||||||
end
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
#!/home/timothypidashev/Desktop/Github/Portfolio/venv/bin/python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from setuptools.command.easy_install 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/timothypidashev/Desktop/Github/Portfolio/venv/bin/python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from setuptools.command.easy_install 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/timothypidashev/Desktop/Github/Portfolio/venv/bin/python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from flask.cli 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/timothypidashev/Desktop/Github/Portfolio/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/timothypidashev/Desktop/Github/Portfolio/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/timothypidashev/Desktop/Github/Portfolio/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 +0,0 @@
|
|||||||
/usr/bin/python3
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
python3
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
from flask import Flask, render_template, request
|
|
||||||
|
|
||||||
app = Flask(__name__, template_folder = "templates", static_url_path="/static")
|
|
||||||
|
|
||||||
#home page
|
|
||||||
@app.route("/")
|
|
||||||
def index():
|
|
||||||
return render_template("index.html")
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
pip
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
Copyright 2010 Pallets
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are
|
|
||||||
met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer in the
|
|
||||||
documentation and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
3. Neither the name of the copyright holder nor the names of its
|
|
||||||
contributors may be used to endorse or promote products derived from
|
|
||||||
this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
|
||||||
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
||||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
||||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
||||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
Metadata-Version: 2.1
|
|
||||||
Name: Flask
|
|
||||||
Version: 2.0.1
|
|
||||||
Summary: A simple framework for building complex web applications.
|
|
||||||
Home-page: https://palletsprojects.com/p/flask
|
|
||||||
Author: Armin Ronacher
|
|
||||||
Author-email: armin.ronacher@active-4.com
|
|
||||||
Maintainer: Pallets
|
|
||||||
Maintainer-email: contact@palletsprojects.com
|
|
||||||
License: BSD-3-Clause
|
|
||||||
Project-URL: Donate, https://palletsprojects.com/donate
|
|
||||||
Project-URL: Documentation, https://flask.palletsprojects.com/
|
|
||||||
Project-URL: Changes, https://flask.palletsprojects.com/changes/
|
|
||||||
Project-URL: Source Code, https://github.com/pallets/flask/
|
|
||||||
Project-URL: Issue Tracker, https://github.com/pallets/flask/issues/
|
|
||||||
Project-URL: Twitter, https://twitter.com/PalletsTeam
|
|
||||||
Project-URL: Chat, https://discord.gg/pallets
|
|
||||||
Platform: UNKNOWN
|
|
||||||
Classifier: Development Status :: 5 - Production/Stable
|
|
||||||
Classifier: Environment :: Web Environment
|
|
||||||
Classifier: Framework :: Flask
|
|
||||||
Classifier: Intended Audience :: Developers
|
|
||||||
Classifier: License :: OSI Approved :: BSD License
|
|
||||||
Classifier: Operating System :: OS Independent
|
|
||||||
Classifier: Programming Language :: Python
|
|
||||||
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
||||||
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI
|
|
||||||
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
|
|
||||||
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
||||||
Requires-Python: >=3.6
|
|
||||||
Description-Content-Type: text/x-rst
|
|
||||||
Requires-Dist: Werkzeug (>=2.0)
|
|
||||||
Requires-Dist: Jinja2 (>=3.0)
|
|
||||||
Requires-Dist: itsdangerous (>=2.0)
|
|
||||||
Requires-Dist: click (>=7.1.2)
|
|
||||||
Provides-Extra: async
|
|
||||||
Requires-Dist: asgiref (>=3.2) ; extra == 'async'
|
|
||||||
Provides-Extra: dotenv
|
|
||||||
Requires-Dist: python-dotenv ; extra == 'dotenv'
|
|
||||||
|
|
||||||
Flask
|
|
||||||
=====
|
|
||||||
|
|
||||||
Flask is a lightweight `WSGI`_ web application framework. It is designed
|
|
||||||
to make getting started quick and easy, with the ability to scale up to
|
|
||||||
complex applications. It began as a simple wrapper around `Werkzeug`_
|
|
||||||
and `Jinja`_ and has become one of the most popular Python web
|
|
||||||
application frameworks.
|
|
||||||
|
|
||||||
Flask offers suggestions, but doesn't enforce any dependencies or
|
|
||||||
project layout. It is up to the developer to choose the tools and
|
|
||||||
libraries they want to use. There are many extensions provided by the
|
|
||||||
community that make adding new functionality easy.
|
|
||||||
|
|
||||||
.. _WSGI: https://wsgi.readthedocs.io/
|
|
||||||
.. _Werkzeug: https://werkzeug.palletsprojects.com/
|
|
||||||
.. _Jinja: https://jinja.palletsprojects.com/
|
|
||||||
|
|
||||||
|
|
||||||
Installing
|
|
||||||
----------
|
|
||||||
|
|
||||||
Install and update using `pip`_:
|
|
||||||
|
|
||||||
.. code-block:: text
|
|
||||||
|
|
||||||
$ pip install -U Flask
|
|
||||||
|
|
||||||
.. _pip: https://pip.pypa.io/en/stable/quickstart/
|
|
||||||
|
|
||||||
|
|
||||||
A Simple Example
|
|
||||||
----------------
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
# save this as app.py
|
|
||||||
from flask import Flask
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
|
||||||
|
|
||||||
@app.route("/")
|
|
||||||
def hello():
|
|
||||||
return "Hello, World!"
|
|
||||||
|
|
||||||
.. code-block:: text
|
|
||||||
|
|
||||||
$ flask run
|
|
||||||
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
|
|
||||||
|
|
||||||
|
|
||||||
Contributing
|
|
||||||
------------
|
|
||||||
|
|
||||||
For guidance on setting up a development environment and how to make a
|
|
||||||
contribution to Flask, see the `contributing guidelines`_.
|
|
||||||
|
|
||||||
.. _contributing guidelines: https://github.com/pallets/flask/blob/main/CONTRIBUTING.rst
|
|
||||||
|
|
||||||
|
|
||||||
Donate
|
|
||||||
------
|
|
||||||
|
|
||||||
The Pallets organization develops and supports Flask and the libraries
|
|
||||||
it uses. In order to grow the community of contributors and users, and
|
|
||||||
allow the maintainers to devote more time to the projects, `please
|
|
||||||
donate today`_.
|
|
||||||
|
|
||||||
.. _please donate today: https://palletsprojects.com/donate
|
|
||||||
|
|
||||||
|
|
||||||
Links
|
|
||||||
-----
|
|
||||||
|
|
||||||
- Documentation: https://flask.palletsprojects.com/
|
|
||||||
- Changes: https://flask.palletsprojects.com/changes/
|
|
||||||
- PyPI Releases: https://pypi.org/project/Flask/
|
|
||||||
- Source Code: https://github.com/pallets/flask/
|
|
||||||
- Issue Tracker: https://github.com/pallets/flask/issues/
|
|
||||||
- Website: https://palletsprojects.com/p/flask/
|
|
||||||
- Twitter: https://twitter.com/PalletsTeam
|
|
||||||
- Chat: https://discord.gg/pallets
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
../../../bin/flask,sha256=oKXJDZPqk6BN2kppYhY7euvhskduDexwetAAkeqfyfg,255
|
|
||||||
Flask-2.0.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
|
||||||
Flask-2.0.1.dist-info/LICENSE.rst,sha256=SJqOEQhQntmKN7uYPhHg9-HTHwvY-Zp5yESOf_N9B-o,1475
|
|
||||||
Flask-2.0.1.dist-info/METADATA,sha256=50Jm1647RKw98p4RF64bCqRh0wajk-n3hQ7av2-pniA,3808
|
|
||||||
Flask-2.0.1.dist-info/RECORD,,
|
|
||||||
Flask-2.0.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
||||||
Flask-2.0.1.dist-info/WHEEL,sha256=OqRkF0eY5GHssMorFjlbTIq072vpHpF60fIQA6lS9xA,92
|
|
||||||
Flask-2.0.1.dist-info/entry_points.txt,sha256=gBLA1aKg0OYR8AhbAfg8lnburHtKcgJLDU52BBctN0k,42
|
|
||||||
Flask-2.0.1.dist-info/top_level.txt,sha256=dvi65F6AeGWVU0TBpYiC04yM60-FX1gJFkK31IKQr5c,6
|
|
||||||
flask/__init__.py,sha256=w5v6GCNm8eLDMNWqs2ue7HLHo75aslAwz1h3k3YO9HY,2251
|
|
||||||
flask/__main__.py,sha256=bYt9eEaoRQWdejEHFD8REx9jxVEdZptECFsV7F49Ink,30
|
|
||||||
flask/__pycache__/__init__.cpython-39.pyc,,
|
|
||||||
flask/__pycache__/__main__.cpython-39.pyc,,
|
|
||||||
flask/__pycache__/app.cpython-39.pyc,,
|
|
||||||
flask/__pycache__/blueprints.cpython-39.pyc,,
|
|
||||||
flask/__pycache__/cli.cpython-39.pyc,,
|
|
||||||
flask/__pycache__/config.cpython-39.pyc,,
|
|
||||||
flask/__pycache__/ctx.cpython-39.pyc,,
|
|
||||||
flask/__pycache__/debughelpers.cpython-39.pyc,,
|
|
||||||
flask/__pycache__/globals.cpython-39.pyc,,
|
|
||||||
flask/__pycache__/helpers.cpython-39.pyc,,
|
|
||||||
flask/__pycache__/logging.cpython-39.pyc,,
|
|
||||||
flask/__pycache__/scaffold.cpython-39.pyc,,
|
|
||||||
flask/__pycache__/sessions.cpython-39.pyc,,
|
|
||||||
flask/__pycache__/signals.cpython-39.pyc,,
|
|
||||||
flask/__pycache__/templating.cpython-39.pyc,,
|
|
||||||
flask/__pycache__/testing.cpython-39.pyc,,
|
|
||||||
flask/__pycache__/typing.cpython-39.pyc,,
|
|
||||||
flask/__pycache__/views.cpython-39.pyc,,
|
|
||||||
flask/__pycache__/wrappers.cpython-39.pyc,,
|
|
||||||
flask/app.py,sha256=q6lpiiWVxjljQRwjjneUBpfllXYPEq0CFAUpTQ5gIeA,82376
|
|
||||||
flask/blueprints.py,sha256=OjI-dkwx96ZNMUGDDFMKzgcpUJf240WRuMlHkmgI1Lc,23541
|
|
||||||
flask/cli.py,sha256=iN1pL2SevE5Nmvey-0WwnxG3nipZXIiE__Ed4lx3IuM,32036
|
|
||||||
flask/config.py,sha256=jj_7JGen_kYuTlKrx8ZPBsZddb8mihC0ODg4gcjXBX8,11068
|
|
||||||
flask/ctx.py,sha256=EM3W0v1ctuFQAGk_HWtQdoJEg_r2f5Le4xcmElxFwwk,17428
|
|
||||||
flask/debughelpers.py,sha256=wk5HtLwENsQ4e8tkxfBn6ykUeVRDuMbQCKgtEVe6jxk,6171
|
|
||||||
flask/globals.py,sha256=cWd-R2hUH3VqPhnmQNww892tQS6Yjqg_wg8UvW1M7NM,1723
|
|
||||||
flask/helpers.py,sha256=00WqA3wYeyjMrnAOPZTUyrnUf7H8ik3CVT0kqGl_qjk,30589
|
|
||||||
flask/json/__init__.py,sha256=d-db2DJMASq0G7CI-JvobehRE1asNRGX1rIDQ1GF9WM,11580
|
|
||||||
flask/json/__pycache__/__init__.cpython-39.pyc,,
|
|
||||||
flask/json/__pycache__/tag.cpython-39.pyc,,
|
|
||||||
flask/json/tag.py,sha256=fys3HBLssWHuMAIJuTcf2K0bCtosePBKXIWASZEEjnU,8857
|
|
||||||
flask/logging.py,sha256=1o_hirVGqdj7SBdETnhX7IAjklG89RXlrwz_2CjzQQE,2273
|
|
||||||
flask/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
||||||
flask/scaffold.py,sha256=EhQuiFrdcmJHxqPGQkEpqLsEUZ7ULZD0rtED2NrduvM,32400
|
|
||||||
flask/sessions.py,sha256=Kb7zY4qBIOU2cw1xM5mQ_KmgYUBDFbUYWjlkq0EFYis,15189
|
|
||||||
flask/signals.py,sha256=HQWgBEXlrLbHwLBoWqAStJKcN-rsB1_AMO8-VZ7LDOo,2126
|
|
||||||
flask/templating.py,sha256=l96VD39JQ0nue4Bcj7wZ4-FWWs-ppLxvgBCpwDQ4KAk,5626
|
|
||||||
flask/testing.py,sha256=OsHT-2B70abWH3ulY9IbhLchXIeyj3L-cfcDa88wv5E,10281
|
|
||||||
flask/typing.py,sha256=zVqhz53KklncAv-WxbpxGZfaRGOqeWAsLdP1tTMaCuE,1684
|
|
||||||
flask/views.py,sha256=F2PpWPloe4x0906IUjnPcsOqg5YvmQIfk07_lFeAD4s,5865
|
|
||||||
flask/wrappers.py,sha256=VndbHPRBSUUOejmd2Y3ydkoCVUtsS2OJIdJEVIkBVD8,5604
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
Wheel-Version: 1.0
|
|
||||||
Generator: bdist_wheel (0.36.2)
|
|
||||||
Root-Is-Purelib: true
|
|
||||||
Tag: py3-none-any
|
|
||||||
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
[console_scripts]
|
|
||||||
flask = flask.cli:main
|
|
||||||
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
flask
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
pip
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
Copyright 2007 Pallets
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are
|
|
||||||
met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer in the
|
|
||||||
documentation and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
3. Neither the name of the copyright holder nor the names of its
|
|
||||||
contributors may be used to endorse or promote products derived from
|
|
||||||
this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
|
||||||
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
||||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
||||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
||||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
Metadata-Version: 2.1
|
|
||||||
Name: Jinja2
|
|
||||||
Version: 3.0.1
|
|
||||||
Summary: A very fast and expressive template engine.
|
|
||||||
Home-page: https://palletsprojects.com/p/jinja/
|
|
||||||
Author: Armin Ronacher
|
|
||||||
Author-email: armin.ronacher@active-4.com
|
|
||||||
Maintainer: Pallets
|
|
||||||
Maintainer-email: contact@palletsprojects.com
|
|
||||||
License: BSD-3-Clause
|
|
||||||
Project-URL: Donate, https://palletsprojects.com/donate
|
|
||||||
Project-URL: Documentation, https://jinja.palletsprojects.com/
|
|
||||||
Project-URL: Changes, https://jinja.palletsprojects.com/changes/
|
|
||||||
Project-URL: Source Code, https://github.com/pallets/jinja/
|
|
||||||
Project-URL: Issue Tracker, https://github.com/pallets/jinja/issues/
|
|
||||||
Project-URL: Twitter, https://twitter.com/PalletsTeam
|
|
||||||
Project-URL: Chat, https://discord.gg/pallets
|
|
||||||
Platform: UNKNOWN
|
|
||||||
Classifier: Development Status :: 5 - Production/Stable
|
|
||||||
Classifier: Environment :: Web Environment
|
|
||||||
Classifier: Intended Audience :: Developers
|
|
||||||
Classifier: License :: OSI Approved :: BSD License
|
|
||||||
Classifier: Operating System :: OS Independent
|
|
||||||
Classifier: Programming Language :: Python
|
|
||||||
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
||||||
Classifier: Topic :: Text Processing :: Markup :: HTML
|
|
||||||
Requires-Python: >=3.6
|
|
||||||
Description-Content-Type: text/x-rst
|
|
||||||
Requires-Dist: MarkupSafe (>=2.0)
|
|
||||||
Provides-Extra: i18n
|
|
||||||
Requires-Dist: Babel (>=2.7) ; extra == 'i18n'
|
|
||||||
|
|
||||||
Jinja
|
|
||||||
=====
|
|
||||||
|
|
||||||
Jinja is a fast, expressive, extensible templating engine. Special
|
|
||||||
placeholders in the template allow writing code similar to Python
|
|
||||||
syntax. Then the template is passed data to render the final document.
|
|
||||||
|
|
||||||
It includes:
|
|
||||||
|
|
||||||
- Template inheritance and inclusion.
|
|
||||||
- Define and import macros within templates.
|
|
||||||
- HTML templates can use autoescaping to prevent XSS from untrusted
|
|
||||||
user input.
|
|
||||||
- A sandboxed environment can safely render untrusted templates.
|
|
||||||
- AsyncIO support for generating templates and calling async
|
|
||||||
functions.
|
|
||||||
- I18N support with Babel.
|
|
||||||
- Templates are compiled to optimized Python code just-in-time and
|
|
||||||
cached, or can be compiled ahead-of-time.
|
|
||||||
- Exceptions point to the correct line in templates to make debugging
|
|
||||||
easier.
|
|
||||||
- Extensible filters, tests, functions, and even syntax.
|
|
||||||
|
|
||||||
Jinja's philosophy is that while application logic belongs in Python if
|
|
||||||
possible, it shouldn't make the template designer's job difficult by
|
|
||||||
restricting functionality too much.
|
|
||||||
|
|
||||||
|
|
||||||
Installing
|
|
||||||
----------
|
|
||||||
|
|
||||||
Install and update using `pip`_:
|
|
||||||
|
|
||||||
.. code-block:: text
|
|
||||||
|
|
||||||
$ pip install -U Jinja2
|
|
||||||
|
|
||||||
.. _pip: https://pip.pypa.io/en/stable/quickstart/
|
|
||||||
|
|
||||||
|
|
||||||
In A Nutshell
|
|
||||||
-------------
|
|
||||||
|
|
||||||
.. code-block:: jinja
|
|
||||||
|
|
||||||
{% extends "base.html" %}
|
|
||||||
{% block title %}Members{% endblock %}
|
|
||||||
{% block content %}
|
|
||||||
<ul>
|
|
||||||
{% for user in users %}
|
|
||||||
<li><a href="{{ user.url }}">{{ user.username }}</a></li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
|
|
||||||
Donate
|
|
||||||
------
|
|
||||||
|
|
||||||
The Pallets organization develops and supports Jinja and other popular
|
|
||||||
packages. In order to grow the community of contributors and users, and
|
|
||||||
allow the maintainers to devote more time to the projects, `please
|
|
||||||
donate today`_.
|
|
||||||
|
|
||||||
.. _please donate today: https://palletsprojects.com/donate
|
|
||||||
|
|
||||||
|
|
||||||
Links
|
|
||||||
-----
|
|
||||||
|
|
||||||
- Documentation: https://jinja.palletsprojects.com/
|
|
||||||
- Changes: https://jinja.palletsprojects.com/changes/
|
|
||||||
- PyPI Releases: https://pypi.org/project/Jinja2/
|
|
||||||
- Source Code: https://github.com/pallets/jinja/
|
|
||||||
- Issue Tracker: https://github.com/pallets/jinja/issues/
|
|
||||||
- Website: https://palletsprojects.com/p/jinja/
|
|
||||||
- Twitter: https://twitter.com/PalletsTeam
|
|
||||||
- Chat: https://discord.gg/pallets
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
Jinja2-3.0.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
|
||||||
Jinja2-3.0.1.dist-info/LICENSE.rst,sha256=O0nc7kEF6ze6wQ-vG-JgQI_oXSUrjp3y4JefweCUQ3s,1475
|
|
||||||
Jinja2-3.0.1.dist-info/METADATA,sha256=k6STiOONbGESP2rEKmjhznuG10vm9sNCHCUQL9AQFM4,3508
|
|
||||||
Jinja2-3.0.1.dist-info/RECORD,,
|
|
||||||
Jinja2-3.0.1.dist-info/WHEEL,sha256=OqRkF0eY5GHssMorFjlbTIq072vpHpF60fIQA6lS9xA,92
|
|
||||||
Jinja2-3.0.1.dist-info/entry_points.txt,sha256=Qy_DkVo6Xj_zzOtmErrATe8lHZhOqdjpt3e4JJAGyi8,61
|
|
||||||
Jinja2-3.0.1.dist-info/top_level.txt,sha256=PkeVWtLb3-CqjWi1fO29OCbj55EhX_chhKrCdrVe_zs,7
|
|
||||||
jinja2/__init__.py,sha256=fd8jaCRsCATgC7ahuUTD8CyfQoc4aRfALEIny4mwfog,2205
|
|
||||||
jinja2/__pycache__/__init__.cpython-39.pyc,,
|
|
||||||
jinja2/__pycache__/_identifier.cpython-39.pyc,,
|
|
||||||
jinja2/__pycache__/async_utils.cpython-39.pyc,,
|
|
||||||
jinja2/__pycache__/bccache.cpython-39.pyc,,
|
|
||||||
jinja2/__pycache__/compiler.cpython-39.pyc,,
|
|
||||||
jinja2/__pycache__/constants.cpython-39.pyc,,
|
|
||||||
jinja2/__pycache__/debug.cpython-39.pyc,,
|
|
||||||
jinja2/__pycache__/defaults.cpython-39.pyc,,
|
|
||||||
jinja2/__pycache__/environment.cpython-39.pyc,,
|
|
||||||
jinja2/__pycache__/exceptions.cpython-39.pyc,,
|
|
||||||
jinja2/__pycache__/ext.cpython-39.pyc,,
|
|
||||||
jinja2/__pycache__/filters.cpython-39.pyc,,
|
|
||||||
jinja2/__pycache__/idtracking.cpython-39.pyc,,
|
|
||||||
jinja2/__pycache__/lexer.cpython-39.pyc,,
|
|
||||||
jinja2/__pycache__/loaders.cpython-39.pyc,,
|
|
||||||
jinja2/__pycache__/meta.cpython-39.pyc,,
|
|
||||||
jinja2/__pycache__/nativetypes.cpython-39.pyc,,
|
|
||||||
jinja2/__pycache__/nodes.cpython-39.pyc,,
|
|
||||||
jinja2/__pycache__/optimizer.cpython-39.pyc,,
|
|
||||||
jinja2/__pycache__/parser.cpython-39.pyc,,
|
|
||||||
jinja2/__pycache__/runtime.cpython-39.pyc,,
|
|
||||||
jinja2/__pycache__/sandbox.cpython-39.pyc,,
|
|
||||||
jinja2/__pycache__/tests.cpython-39.pyc,,
|
|
||||||
jinja2/__pycache__/utils.cpython-39.pyc,,
|
|
||||||
jinja2/__pycache__/visitor.cpython-39.pyc,,
|
|
||||||
jinja2/_identifier.py,sha256=EdgGJKi7O1yvr4yFlvqPNEqV6M1qHyQr8Gt8GmVTKVM,1775
|
|
||||||
jinja2/async_utils.py,sha256=bY2nCUfBA_4FSnNUsIsJgljBq3hACr6fzLi7LiyMTn8,1751
|
|
||||||
jinja2/bccache.py,sha256=smAvSDgDSvXdvJzCN_9s0XfkVpQEu8be-QwgeMlrwiM,12677
|
|
||||||
jinja2/compiler.py,sha256=qq0Fo9EpDAEwHPLAs3sAP7dindUvDrFrbx4AcB8xV5M,72046
|
|
||||||
jinja2/constants.py,sha256=GMoFydBF_kdpaRKPoM5cl5MviquVRLVyZtfp5-16jg0,1433
|
|
||||||
jinja2/debug.py,sha256=uBmrsiwjYH5l14R9STn5mydOOyriBYol5lDGvEqAb3A,9238
|
|
||||||
jinja2/defaults.py,sha256=boBcSw78h-lp20YbaXSJsqkAI2uN_mD_TtCydpeq5wU,1267
|
|
||||||
jinja2/environment.py,sha256=T6U4be9mY1CUXXin_EQFwpvpFqCiryweGqzXGRYIoSA,61573
|
|
||||||
jinja2/exceptions.py,sha256=ioHeHrWwCWNaXX1inHmHVblvc4haO7AXsjCp3GfWvx0,5071
|
|
||||||
jinja2/ext.py,sha256=44SjDjeYkkxQTpmC2BetOTxEFMgQ42p2dfSwXmPFcSo,32122
|
|
||||||
jinja2/filters.py,sha256=LslRsJd0JVFBHtdfU_WraM1eQitotciwawiW-seR42U,52577
|
|
||||||
jinja2/idtracking.py,sha256=KdFVohVNK-baOwt_INPMco19D7AfLDEN8i3_JoiYnGQ,10713
|
|
||||||
jinja2/lexer.py,sha256=D5qOKB3KnRqK9gPAZFQvRguomYsQok5-14TKiWTN8Jw,29923
|
|
||||||
jinja2/loaders.py,sha256=ePpWB0xDrILgLVqNFcxqqSbPizsI0T-JlkNEUFqq9fo,22350
|
|
||||||
jinja2/meta.py,sha256=GNPEvifmSaU3CMxlbheBOZjeZ277HThOPUTf1RkppKQ,4396
|
|
||||||
jinja2/nativetypes.py,sha256=62hvvsAxAj0YaxylOHoREYVogJ5JqOlJISgGY3OKd_o,3675
|
|
||||||
jinja2/nodes.py,sha256=LHF97fu6GW4r2Z9UaOX92MOT1wZpdS9Nx4N-5Fp5ti8,34509
|
|
||||||
jinja2/optimizer.py,sha256=tHkMwXxfZkbfA1KmLcqmBMSaz7RLIvvItrJcPoXTyD8,1650
|
|
||||||
jinja2/parser.py,sha256=kHnU8v92GwMYkfr0MVakWv8UlSf_kJPx8LUsgQMof70,39767
|
|
||||||
jinja2/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
||||||
jinja2/runtime.py,sha256=bSWdawLjReKpKHhF3-96OIuWYpUy1yxFJCN3jBYyoXc,35013
|
|
||||||
jinja2/sandbox.py,sha256=-8zxR6TO9kUkciAVFsIKu8Oq-C7PTeYEdZ5TtA55-gw,14600
|
|
||||||
jinja2/tests.py,sha256=Am5Z6Lmfr2XaH_npIfJJ8MdXtWsbLjMULZJulTAj30E,5905
|
|
||||||
jinja2/utils.py,sha256=0wGkxDbxlW10y0ac4-kEiy1Bn0AsWXqz8uomK9Ugy1Q,26961
|
|
||||||
jinja2/visitor.py,sha256=ZmeLuTj66ic35-uFH-1m0EKXiw4ObDDb_WuE6h5vPFg,3572
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
Wheel-Version: 1.0
|
|
||||||
Generator: bdist_wheel (0.36.2)
|
|
||||||
Root-Is-Purelib: true
|
|
||||||
Tag: py3-none-any
|
|
||||||
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
[babel.extractors]
|
|
||||||
jinja2 = jinja2.ext:babel_extract [i18n]
|
|
||||||
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
jinja2
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
pip
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
Copyright 2010 Pallets
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are
|
|
||||||
met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer in the
|
|
||||||
documentation and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
3. Neither the name of the copyright holder nor the names of its
|
|
||||||
contributors may be used to endorse or promote products derived from
|
|
||||||
this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
|
||||||
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
||||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
||||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
||||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
Metadata-Version: 2.1
|
|
||||||
Name: MarkupSafe
|
|
||||||
Version: 2.0.1
|
|
||||||
Summary: Safely add untrusted strings to HTML/XML markup.
|
|
||||||
Home-page: https://palletsprojects.com/p/markupsafe/
|
|
||||||
Author: Armin Ronacher
|
|
||||||
Author-email: armin.ronacher@active-4.com
|
|
||||||
Maintainer: Pallets
|
|
||||||
Maintainer-email: contact@palletsprojects.com
|
|
||||||
License: BSD-3-Clause
|
|
||||||
Project-URL: Donate, https://palletsprojects.com/donate
|
|
||||||
Project-URL: Documentation, https://markupsafe.palletsprojects.com/
|
|
||||||
Project-URL: Changes, https://markupsafe.palletsprojects.com/changes/
|
|
||||||
Project-URL: Source Code, https://github.com/pallets/markupsafe/
|
|
||||||
Project-URL: Issue Tracker, https://github.com/pallets/markupsafe/issues/
|
|
||||||
Project-URL: Twitter, https://twitter.com/PalletsTeam
|
|
||||||
Project-URL: Chat, https://discord.gg/pallets
|
|
||||||
Platform: UNKNOWN
|
|
||||||
Classifier: Development Status :: 5 - Production/Stable
|
|
||||||
Classifier: Environment :: Web Environment
|
|
||||||
Classifier: Intended Audience :: Developers
|
|
||||||
Classifier: License :: OSI Approved :: BSD License
|
|
||||||
Classifier: Operating System :: OS Independent
|
|
||||||
Classifier: Programming Language :: Python
|
|
||||||
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
||||||
Classifier: Topic :: Text Processing :: Markup :: HTML
|
|
||||||
Requires-Python: >=3.6
|
|
||||||
Description-Content-Type: text/x-rst
|
|
||||||
|
|
||||||
MarkupSafe
|
|
||||||
==========
|
|
||||||
|
|
||||||
MarkupSafe implements a text object that escapes characters so it is
|
|
||||||
safe to use in HTML and XML. Characters that have special meanings are
|
|
||||||
replaced so that they display as the actual characters. This mitigates
|
|
||||||
injection attacks, meaning untrusted user input can safely be displayed
|
|
||||||
on a page.
|
|
||||||
|
|
||||||
|
|
||||||
Installing
|
|
||||||
----------
|
|
||||||
|
|
||||||
Install and update using `pip`_:
|
|
||||||
|
|
||||||
.. code-block:: text
|
|
||||||
|
|
||||||
pip install -U MarkupSafe
|
|
||||||
|
|
||||||
.. _pip: https://pip.pypa.io/en/stable/quickstart/
|
|
||||||
|
|
||||||
|
|
||||||
Examples
|
|
||||||
--------
|
|
||||||
|
|
||||||
.. code-block:: pycon
|
|
||||||
|
|
||||||
>>> from markupsafe import Markup, escape
|
|
||||||
|
|
||||||
>>> # escape replaces special characters and wraps in Markup
|
|
||||||
>>> escape("<script>alert(document.cookie);</script>")
|
|
||||||
Markup('<script>alert(document.cookie);</script>')
|
|
||||||
|
|
||||||
>>> # wrap in Markup to mark text "safe" and prevent escaping
|
|
||||||
>>> Markup("<strong>Hello</strong>")
|
|
||||||
Markup('<strong>hello</strong>')
|
|
||||||
|
|
||||||
>>> escape(Markup("<strong>Hello</strong>"))
|
|
||||||
Markup('<strong>hello</strong>')
|
|
||||||
|
|
||||||
>>> # Markup is a str subclass
|
|
||||||
>>> # methods and operators escape their arguments
|
|
||||||
>>> template = Markup("Hello <em>{name}</em>")
|
|
||||||
>>> template.format(name='"World"')
|
|
||||||
Markup('Hello <em>"World"</em>')
|
|
||||||
|
|
||||||
|
|
||||||
Donate
|
|
||||||
------
|
|
||||||
|
|
||||||
The Pallets organization develops and supports MarkupSafe and other
|
|
||||||
popular packages. In order to grow the community of contributors and
|
|
||||||
users, and allow the maintainers to devote more time to the projects,
|
|
||||||
`please donate today`_.
|
|
||||||
|
|
||||||
.. _please donate today: https://palletsprojects.com/donate
|
|
||||||
|
|
||||||
|
|
||||||
Links
|
|
||||||
-----
|
|
||||||
|
|
||||||
- Documentation: https://markupsafe.palletsprojects.com/
|
|
||||||
- Changes: https://markupsafe.palletsprojects.com/changes/
|
|
||||||
- PyPI Releases: https://pypi.org/project/MarkupSafe/
|
|
||||||
- Source Code: https://github.com/pallets/markupsafe/
|
|
||||||
- Issue Tracker: https://github.com/pallets/markupsafe/issues/
|
|
||||||
- Website: https://palletsprojects.com/p/markupsafe/
|
|
||||||
- Twitter: https://twitter.com/PalletsTeam
|
|
||||||
- Chat: https://discord.gg/pallets
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
MarkupSafe-2.0.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
|
||||||
MarkupSafe-2.0.1.dist-info/LICENSE.rst,sha256=SJqOEQhQntmKN7uYPhHg9-HTHwvY-Zp5yESOf_N9B-o,1475
|
|
||||||
MarkupSafe-2.0.1.dist-info/METADATA,sha256=FmPpxBdaqCCjF-XKqoxeEzqAzhetQnrkkSsd3V3X-Jc,3211
|
|
||||||
MarkupSafe-2.0.1.dist-info/RECORD,,
|
|
||||||
MarkupSafe-2.0.1.dist-info/WHEEL,sha256=C-sg6l2ppbqlkU_0fUt0o5fTSvsM-h9TfVKAh4ryMfI,111
|
|
||||||
MarkupSafe-2.0.1.dist-info/top_level.txt,sha256=qy0Plje5IJuvsCBjejJyhDCjEAdcDLK_2agVcex8Z6U,11
|
|
||||||
markupsafe/__init__.py,sha256=9Tez4UIlI7J6_sQcUFK1dKniT_b_8YefpGIyYJ3Sr2Q,8923
|
|
||||||
markupsafe/__pycache__/__init__.cpython-39.pyc,,
|
|
||||||
markupsafe/__pycache__/_native.cpython-39.pyc,,
|
|
||||||
markupsafe/_native.py,sha256=GTKEV-bWgZuSjklhMHOYRHU9k0DMewTf5mVEZfkbuns,1986
|
|
||||||
markupsafe/_speedups.c,sha256=CDDtwaV21D2nYtypnMQzxvvpZpcTvIs8OZ6KDa1g4t0,7400
|
|
||||||
markupsafe/_speedups.cpython-39-x86_64-linux-gnu.so,sha256=eGr-sqbWsXEBzb2iypAevZQXZFzkverTnd_GX5lvgnM,53224
|
|
||||||
markupsafe/_speedups.pyi,sha256=vfMCsOgbAXRNLUXkyuyonG8uEWKYU4PDqNuMaDELAYw,229
|
|
||||||
markupsafe/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
Wheel-Version: 1.0
|
|
||||||
Generator: bdist_wheel (0.36.2)
|
|
||||||
Root-Is-Purelib: false
|
|
||||||
Tag: cp39-cp39-manylinux2010_x86_64
|
|
||||||
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
markupsafe
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
pip
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
Copyright 2007 Pallets
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are
|
|
||||||
met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer in the
|
|
||||||
documentation and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
3. Neither the name of the copyright holder nor the names of its
|
|
||||||
contributors may be used to endorse or promote products derived from
|
|
||||||
this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
|
||||||
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
||||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
||||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
||||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
Metadata-Version: 2.1
|
|
||||||
Name: Werkzeug
|
|
||||||
Version: 2.0.1
|
|
||||||
Summary: The comprehensive WSGI web application library.
|
|
||||||
Home-page: https://palletsprojects.com/p/werkzeug/
|
|
||||||
Author: Armin Ronacher
|
|
||||||
Author-email: armin.ronacher@active-4.com
|
|
||||||
Maintainer: Pallets
|
|
||||||
Maintainer-email: contact@palletsprojects.com
|
|
||||||
License: BSD-3-Clause
|
|
||||||
Project-URL: Donate, https://palletsprojects.com/donate
|
|
||||||
Project-URL: Documentation, https://werkzeug.palletsprojects.com/
|
|
||||||
Project-URL: Changes, https://werkzeug.palletsprojects.com/changes/
|
|
||||||
Project-URL: Source Code, https://github.com/pallets/werkzeug/
|
|
||||||
Project-URL: Issue Tracker, https://github.com/pallets/werkzeug/issues/
|
|
||||||
Project-URL: Twitter, https://twitter.com/PalletsTeam
|
|
||||||
Project-URL: Chat, https://discord.gg/pallets
|
|
||||||
Platform: UNKNOWN
|
|
||||||
Classifier: Development Status :: 5 - Production/Stable
|
|
||||||
Classifier: Environment :: Web Environment
|
|
||||||
Classifier: Intended Audience :: Developers
|
|
||||||
Classifier: License :: OSI Approved :: BSD License
|
|
||||||
Classifier: Operating System :: OS Independent
|
|
||||||
Classifier: Programming Language :: Python
|
|
||||||
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
||||||
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI
|
|
||||||
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
|
|
||||||
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware
|
|
||||||
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
||||||
Requires-Python: >=3.6
|
|
||||||
Description-Content-Type: text/x-rst
|
|
||||||
Requires-Dist: dataclasses ; python_version < "3.7"
|
|
||||||
Provides-Extra: watchdog
|
|
||||||
Requires-Dist: watchdog ; extra == 'watchdog'
|
|
||||||
|
|
||||||
Werkzeug
|
|
||||||
========
|
|
||||||
|
|
||||||
*werkzeug* German noun: "tool". Etymology: *werk* ("work"), *zeug* ("stuff")
|
|
||||||
|
|
||||||
Werkzeug is a comprehensive `WSGI`_ web application library. It began as
|
|
||||||
a simple collection of various utilities for WSGI applications and has
|
|
||||||
become one of the most advanced WSGI utility libraries.
|
|
||||||
|
|
||||||
It includes:
|
|
||||||
|
|
||||||
- An interactive debugger that allows inspecting stack traces and
|
|
||||||
source code in the browser with an interactive interpreter for any
|
|
||||||
frame in the stack.
|
|
||||||
- A full-featured request object with objects to interact with
|
|
||||||
headers, query args, form data, files, and cookies.
|
|
||||||
- A response object that can wrap other WSGI applications and handle
|
|
||||||
streaming data.
|
|
||||||
- A routing system for matching URLs to endpoints and generating URLs
|
|
||||||
for endpoints, with an extensible system for capturing variables
|
|
||||||
from URLs.
|
|
||||||
- HTTP utilities to handle entity tags, cache control, dates, user
|
|
||||||
agents, cookies, files, and more.
|
|
||||||
- A threaded WSGI server for use while developing applications
|
|
||||||
locally.
|
|
||||||
- A test client for simulating HTTP requests during testing without
|
|
||||||
requiring running a server.
|
|
||||||
|
|
||||||
Werkzeug doesn't enforce any dependencies. It is up to the developer to
|
|
||||||
choose a template engine, database adapter, and even how to handle
|
|
||||||
requests. It can be used to build all sorts of end user applications
|
|
||||||
such as blogs, wikis, or bulletin boards.
|
|
||||||
|
|
||||||
`Flask`_ wraps Werkzeug, using it to handle the details of WSGI while
|
|
||||||
providing more structure and patterns for defining powerful
|
|
||||||
applications.
|
|
||||||
|
|
||||||
.. _WSGI: https://wsgi.readthedocs.io/en/latest/
|
|
||||||
.. _Flask: https://www.palletsprojects.com/p/flask/
|
|
||||||
|
|
||||||
|
|
||||||
Installing
|
|
||||||
----------
|
|
||||||
|
|
||||||
Install and update using `pip`_:
|
|
||||||
|
|
||||||
.. code-block:: text
|
|
||||||
|
|
||||||
pip install -U Werkzeug
|
|
||||||
|
|
||||||
.. _pip: https://pip.pypa.io/en/stable/quickstart/
|
|
||||||
|
|
||||||
|
|
||||||
A Simple Example
|
|
||||||
----------------
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
from werkzeug.wrappers import Request, Response
|
|
||||||
|
|
||||||
@Request.application
|
|
||||||
def application(request):
|
|
||||||
return Response('Hello, World!')
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
from werkzeug.serving import run_simple
|
|
||||||
run_simple('localhost', 4000, application)
|
|
||||||
|
|
||||||
|
|
||||||
Donate
|
|
||||||
------
|
|
||||||
|
|
||||||
The Pallets organization develops and supports Werkzeug and other
|
|
||||||
popular packages. In order to grow the community of contributors and
|
|
||||||
users, and allow the maintainers to devote more time to the projects,
|
|
||||||
`please donate today`_.
|
|
||||||
|
|
||||||
.. _please donate today: https://palletsprojects.com/donate
|
|
||||||
|
|
||||||
|
|
||||||
Links
|
|
||||||
-----
|
|
||||||
|
|
||||||
- Documentation: https://werkzeug.palletsprojects.com/
|
|
||||||
- Changes: https://werkzeug.palletsprojects.com/changes/
|
|
||||||
- PyPI Releases: https://pypi.org/project/Werkzeug/
|
|
||||||
- Source Code: https://github.com/pallets/werkzeug/
|
|
||||||
- Issue Tracker: https://github.com/pallets/werkzeug/issues/
|
|
||||||
- Website: https://palletsprojects.com/p/werkzeug/
|
|
||||||
- Twitter: https://twitter.com/PalletsTeam
|
|
||||||
- Chat: https://discord.gg/pallets
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
Werkzeug-2.0.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
|
||||||
Werkzeug-2.0.1.dist-info/LICENSE.rst,sha256=O0nc7kEF6ze6wQ-vG-JgQI_oXSUrjp3y4JefweCUQ3s,1475
|
|
||||||
Werkzeug-2.0.1.dist-info/METADATA,sha256=8-W33EMnGqnCCi-d8Dv63IQQuyELRIsXhwOJNXbNgL0,4421
|
|
||||||
Werkzeug-2.0.1.dist-info/RECORD,,
|
|
||||||
Werkzeug-2.0.1.dist-info/WHEEL,sha256=OqRkF0eY5GHssMorFjlbTIq072vpHpF60fIQA6lS9xA,92
|
|
||||||
Werkzeug-2.0.1.dist-info/top_level.txt,sha256=QRyj2VjwJoQkrwjwFIOlB8Xg3r9un0NtqVHQF-15xaw,9
|
|
||||||
werkzeug/__init__.py,sha256=_CCsfdeqNllFNRJx8cvqYrwBsQQQXJaMmnk2sAZnDng,188
|
|
||||||
werkzeug/__pycache__/__init__.cpython-39.pyc,,
|
|
||||||
werkzeug/__pycache__/_internal.cpython-39.pyc,,
|
|
||||||
werkzeug/__pycache__/_reloader.cpython-39.pyc,,
|
|
||||||
werkzeug/__pycache__/datastructures.cpython-39.pyc,,
|
|
||||||
werkzeug/__pycache__/exceptions.cpython-39.pyc,,
|
|
||||||
werkzeug/__pycache__/filesystem.cpython-39.pyc,,
|
|
||||||
werkzeug/__pycache__/formparser.cpython-39.pyc,,
|
|
||||||
werkzeug/__pycache__/http.cpython-39.pyc,,
|
|
||||||
werkzeug/__pycache__/local.cpython-39.pyc,,
|
|
||||||
werkzeug/__pycache__/routing.cpython-39.pyc,,
|
|
||||||
werkzeug/__pycache__/security.cpython-39.pyc,,
|
|
||||||
werkzeug/__pycache__/serving.cpython-39.pyc,,
|
|
||||||
werkzeug/__pycache__/test.cpython-39.pyc,,
|
|
||||||
werkzeug/__pycache__/testapp.cpython-39.pyc,,
|
|
||||||
werkzeug/__pycache__/urls.cpython-39.pyc,,
|
|
||||||
werkzeug/__pycache__/user_agent.cpython-39.pyc,,
|
|
||||||
werkzeug/__pycache__/useragents.cpython-39.pyc,,
|
|
||||||
werkzeug/__pycache__/utils.cpython-39.pyc,,
|
|
||||||
werkzeug/__pycache__/wsgi.cpython-39.pyc,,
|
|
||||||
werkzeug/_internal.py,sha256=_QKkvdaG4pDFwK68c0EpPzYJGe9Y7toRAT1cBbC-CxU,18572
|
|
||||||
werkzeug/_reloader.py,sha256=B1hEfgsUOz2IginBQM5Zak_eaIF7gr3GS5-0x2OHvAE,13950
|
|
||||||
werkzeug/datastructures.py,sha256=KahVPSLOapbNbKh1ppr9K8_DgWJv1EGgA9DhTEGMHcg,97886
|
|
||||||
werkzeug/datastructures.pyi,sha256=5DTPF8P8Zvi458eK27Qcj7eNUlLM_AC0jBNkj6uQpds,33774
|
|
||||||
werkzeug/debug/__init__.py,sha256=CUFrPEYAaotHRkmjOieqd3EasXDii2JVC1HdmEzMwqM,17924
|
|
||||||
werkzeug/debug/__pycache__/__init__.cpython-39.pyc,,
|
|
||||||
werkzeug/debug/__pycache__/console.cpython-39.pyc,,
|
|
||||||
werkzeug/debug/__pycache__/repr.cpython-39.pyc,,
|
|
||||||
werkzeug/debug/__pycache__/tbtools.cpython-39.pyc,,
|
|
||||||
werkzeug/debug/console.py,sha256=E1nBMEvFkX673ShQjPtVY-byYatfX9MN-dBMjRI8a8E,5897
|
|
||||||
werkzeug/debug/repr.py,sha256=QCSHENKsChEZDCIApkVi_UNjhJ77v8BMXK1OfxO189M,9483
|
|
||||||
werkzeug/debug/shared/FONT_LICENSE,sha256=LwAVEI1oYnvXiNMT9SnCH_TaLCxCpeHziDrMg0gPkAI,4673
|
|
||||||
werkzeug/debug/shared/ICON_LICENSE.md,sha256=DhA6Y1gUl5Jwfg0NFN9Rj4VWITt8tUx0IvdGf0ux9-s,222
|
|
||||||
werkzeug/debug/shared/console.png,sha256=bxax6RXXlvOij_KeqvSNX0ojJf83YbnZ7my-3Gx9w2A,507
|
|
||||||
werkzeug/debug/shared/debugger.js,sha256=dYbUmFmb3YZb5YpWpYPOQArdrN7NPeY0ODawL7ihzDI,10524
|
|
||||||
werkzeug/debug/shared/less.png,sha256=-4-kNRaXJSONVLahrQKUxMwXGm9R4OnZ9SxDGpHlIR4,191
|
|
||||||
werkzeug/debug/shared/more.png,sha256=GngN7CioHQoV58rH6ojnkYi8c_qED2Aka5FO5UXrReY,200
|
|
||||||
werkzeug/debug/shared/source.png,sha256=RoGcBTE4CyCB85GBuDGTFlAnUqxwTBiIfDqW15EpnUQ,818
|
|
||||||
werkzeug/debug/shared/style.css,sha256=vyp1RnB227Fuw8LIyM5C-bBCBQN5hvZSCApY2oeJ9ik,6705
|
|
||||||
werkzeug/debug/shared/ubuntu.ttf,sha256=1eaHFyepmy4FyDvjLVzpITrGEBu_CZYY94jE0nED1c0,70220
|
|
||||||
werkzeug/debug/tbtools.py,sha256=TfReusPbM3yjm3xvOFkH45V7-5JnNqB9x1EQPnVC6Xo,19189
|
|
||||||
werkzeug/exceptions.py,sha256=CUwx0pBiNbk4f9cON17ekgKnmLi6HIVFjUmYZc2x0wM,28681
|
|
||||||
werkzeug/filesystem.py,sha256=JS2Dv2QF98WILxY4_thHl-WMcUcwluF_4igkDPaP1l4,1956
|
|
||||||
werkzeug/formparser.py,sha256=GIKfzuQ_khuBXnf3N7_LzOEruYwNc3m4bI02BgtT5jg,17385
|
|
||||||
werkzeug/http.py,sha256=oUCXFFMnkOQ-cHbUY_aiqitshcrSzNDq3fEMf1VI_yk,45141
|
|
||||||
werkzeug/local.py,sha256=WsR6H-2XOtPigpimjORbLsS3h9WI0lCdZjGI2LHDDxA,22733
|
|
||||||
werkzeug/middleware/__init__.py,sha256=qfqgdT5npwG9ses3-FXQJf3aB95JYP1zchetH_T3PUw,500
|
|
||||||
werkzeug/middleware/__pycache__/__init__.cpython-39.pyc,,
|
|
||||||
werkzeug/middleware/__pycache__/dispatcher.cpython-39.pyc,,
|
|
||||||
werkzeug/middleware/__pycache__/http_proxy.cpython-39.pyc,,
|
|
||||||
werkzeug/middleware/__pycache__/lint.cpython-39.pyc,,
|
|
||||||
werkzeug/middleware/__pycache__/profiler.cpython-39.pyc,,
|
|
||||||
werkzeug/middleware/__pycache__/proxy_fix.cpython-39.pyc,,
|
|
||||||
werkzeug/middleware/__pycache__/shared_data.cpython-39.pyc,,
|
|
||||||
werkzeug/middleware/dispatcher.py,sha256=Fh_w-KyWnTSYF-Lfv5dimQ7THSS7afPAZMmvc4zF1gg,2580
|
|
||||||
werkzeug/middleware/http_proxy.py,sha256=HE8VyhS7CR-E1O6_9b68huv8FLgGGR1DLYqkS3Xcp3Q,7558
|
|
||||||
werkzeug/middleware/lint.py,sha256=yMzMdm4xI2_N-Wv2j1yoaVI3ltHOYS6yZyA-wUv1sKw,13962
|
|
||||||
werkzeug/middleware/profiler.py,sha256=G2JieUMv4QPamtCY6ibIK7P-piPRdPybav7bm2MSFvs,4898
|
|
||||||
werkzeug/middleware/proxy_fix.py,sha256=uRgQ3dEvFV8JxUqajHYYYOPEeA_BFqaa51Yp8VW0uzA,6849
|
|
||||||
werkzeug/middleware/shared_data.py,sha256=eOCGr-i6BCexDfL7xdPRWMwPJPgp0NE2B416Gl67Q78,10959
|
|
||||||
werkzeug/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
||||||
werkzeug/routing.py,sha256=FDRtvCfaZSmXnQ0cUYxowb3P0y0dxlUlMSUmerY5sb0,84147
|
|
||||||
werkzeug/sansio/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
||||||
werkzeug/sansio/__pycache__/__init__.cpython-39.pyc,,
|
|
||||||
werkzeug/sansio/__pycache__/multipart.cpython-39.pyc,,
|
|
||||||
werkzeug/sansio/__pycache__/request.cpython-39.pyc,,
|
|
||||||
werkzeug/sansio/__pycache__/response.cpython-39.pyc,,
|
|
||||||
werkzeug/sansio/__pycache__/utils.cpython-39.pyc,,
|
|
||||||
werkzeug/sansio/multipart.py,sha256=bJMCNC2f5xyAaylahNViJ0JqmV4ThLRbDVGVzKwcqrQ,8751
|
|
||||||
werkzeug/sansio/request.py,sha256=aA9rABkWiG4MhYMByanst2NXkEclsq8SIxhb0LQf0e0,20228
|
|
||||||
werkzeug/sansio/response.py,sha256=HSG6t-tyPZd3awzWqr7qL9IV4HYAvDgON1c0YPU2RXw,24117
|
|
||||||
werkzeug/sansio/utils.py,sha256=V5v-UUnX8pm4RehP9Tt_NiUSOJGJGUvKjlW0eOIQldM,4164
|
|
||||||
werkzeug/security.py,sha256=gPDRuCjkjWrcqj99tBMq8_nHFZLFQjgoW5Ga5XIw9jo,8158
|
|
||||||
werkzeug/serving.py,sha256=_RG2dCclOQcdjJ2NE8tzCRybGePlwcs8kTypiWRP2gY,38030
|
|
||||||
werkzeug/test.py,sha256=EJXJy-b_JriHrlfs5VNCkwbki8Kn_xUDkOYOCx_6Q7Q,48096
|
|
||||||
werkzeug/testapp.py,sha256=f48prWSGJhbSrvYb8e1fnAah4BkrLb0enHSdChgsjBY,9471
|
|
||||||
werkzeug/urls.py,sha256=3o_aUcr5Ou13XihSU6VvX6RHMhoWkKpXuCCia9SSAb8,41021
|
|
||||||
werkzeug/user_agent.py,sha256=WclZhpvgLurMF45hsioSbS75H1Zb4iMQGKN3_yZ2oKo,1420
|
|
||||||
werkzeug/useragents.py,sha256=G8tmv_6vxJaPrLQH3eODNgIYe0_V6KETROQlJI-WxDE,7264
|
|
||||||
werkzeug/utils.py,sha256=WrU-LbwemyGd8zBHBgQyLaIxing4QLEChiP0qnzr2sc,36771
|
|
||||||
werkzeug/wrappers/__init__.py,sha256=-s75nPbyXHzU_rwmLPDhoMuGbEUk0jZT_n0ZQAOFGf8,654
|
|
||||||
werkzeug/wrappers/__pycache__/__init__.cpython-39.pyc,,
|
|
||||||
werkzeug/wrappers/__pycache__/accept.cpython-39.pyc,,
|
|
||||||
werkzeug/wrappers/__pycache__/auth.cpython-39.pyc,,
|
|
||||||
werkzeug/wrappers/__pycache__/base_request.cpython-39.pyc,,
|
|
||||||
werkzeug/wrappers/__pycache__/base_response.cpython-39.pyc,,
|
|
||||||
werkzeug/wrappers/__pycache__/common_descriptors.cpython-39.pyc,,
|
|
||||||
werkzeug/wrappers/__pycache__/cors.cpython-39.pyc,,
|
|
||||||
werkzeug/wrappers/__pycache__/etag.cpython-39.pyc,,
|
|
||||||
werkzeug/wrappers/__pycache__/json.cpython-39.pyc,,
|
|
||||||
werkzeug/wrappers/__pycache__/request.cpython-39.pyc,,
|
|
||||||
werkzeug/wrappers/__pycache__/response.cpython-39.pyc,,
|
|
||||||
werkzeug/wrappers/__pycache__/user_agent.cpython-39.pyc,,
|
|
||||||
werkzeug/wrappers/accept.py,sha256=_oZtAQkahvsrPRkNj2fieg7_St9P0NFC3SgZbJKS6xU,429
|
|
||||||
werkzeug/wrappers/auth.py,sha256=rZPCzGxHk9R55PRkmS90kRywUVjjuMWzCGtH68qCq8U,856
|
|
||||||
werkzeug/wrappers/base_request.py,sha256=saz9RyNQkvI_XLPYVm29KijNHmD1YzgxDqa0qHTbgss,1174
|
|
||||||
werkzeug/wrappers/base_response.py,sha256=q_-TaYywT5G4zA-DWDRDJhJSat2_4O7gOPob6ye4_9A,1186
|
|
||||||
werkzeug/wrappers/common_descriptors.py,sha256=v_kWLH3mvCiSRVJ1FNw7nO3w2UJfzY57UKKB5J4zCvE,898
|
|
||||||
werkzeug/wrappers/cors.py,sha256=c5UndlZsZvYkbPrp6Gj5iSXxw_VOJDJHskO6-jRmNyQ,846
|
|
||||||
werkzeug/wrappers/etag.py,sha256=XHWQQs7Mdd1oWezgBIsl-bYe8ydKkRZVil2Qd01D0Mo,846
|
|
||||||
werkzeug/wrappers/json.py,sha256=HM1btPseGeXca0vnwQN_MvZl6h-qNsFY5YBKXKXFwus,410
|
|
||||||
werkzeug/wrappers/request.py,sha256=0zAkCUwJbUBzioGy2UKxE6XpuXPAZbEhhML4WErzeBo,24818
|
|
||||||
werkzeug/wrappers/response.py,sha256=95hXIysZTeNC0bqhvGB2fLBRKxedR_cgI5szZZWfyzw,35177
|
|
||||||
werkzeug/wrappers/user_agent.py,sha256=Wl1-A0-1r8o7cHIZQTB55O4Ged6LpCKENaQDlOY5pXA,435
|
|
||||||
werkzeug/wsgi.py,sha256=7psV3SHLtCzk1KSuGmIK5uP2QTDXyfCCDclyqCmIUO4,33715
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
Wheel-Version: 1.0
|
|
||||||
Generator: bdist_wheel (0.36.2)
|
|
||||||
Root-Is-Purelib: true
|
|
||||||
Tag: py3-none-any
|
|
||||||
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
werkzeug
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
pip
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
Copyright 2014 Pallets
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are
|
|
||||||
met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer in the
|
|
||||||
documentation and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
3. Neither the name of the copyright holder nor the names of its
|
|
||||||
contributors may be used to endorse or promote products derived from
|
|
||||||
this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
|
||||||
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
||||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
||||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
||||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
Metadata-Version: 2.1
|
|
||||||
Name: click
|
|
||||||
Version: 8.0.1
|
|
||||||
Summary: Composable command line interface toolkit
|
|
||||||
Home-page: https://palletsprojects.com/p/click/
|
|
||||||
Author: Armin Ronacher
|
|
||||||
Author-email: armin.ronacher@active-4.com
|
|
||||||
Maintainer: Pallets
|
|
||||||
Maintainer-email: contact@palletsprojects.com
|
|
||||||
License: BSD-3-Clause
|
|
||||||
Project-URL: Donate, https://palletsprojects.com/donate
|
|
||||||
Project-URL: Documentation, https://click.palletsprojects.com/
|
|
||||||
Project-URL: Changes, https://click.palletsprojects.com/changes/
|
|
||||||
Project-URL: Source Code, https://github.com/pallets/click/
|
|
||||||
Project-URL: Issue Tracker, https://github.com/pallets/click/issues/
|
|
||||||
Project-URL: Twitter, https://twitter.com/PalletsTeam
|
|
||||||
Project-URL: Chat, https://discord.gg/pallets
|
|
||||||
Platform: UNKNOWN
|
|
||||||
Classifier: Development Status :: 5 - Production/Stable
|
|
||||||
Classifier: Intended Audience :: Developers
|
|
||||||
Classifier: License :: OSI Approved :: BSD License
|
|
||||||
Classifier: Operating System :: OS Independent
|
|
||||||
Classifier: Programming Language :: Python
|
|
||||||
Requires-Python: >=3.6
|
|
||||||
Description-Content-Type: text/x-rst
|
|
||||||
Requires-Dist: colorama ; platform_system == "Windows"
|
|
||||||
Requires-Dist: importlib-metadata ; python_version < "3.8"
|
|
||||||
|
|
||||||
\$ click\_
|
|
||||||
==========
|
|
||||||
|
|
||||||
Click is a Python package for creating beautiful command line interfaces
|
|
||||||
in a composable way with as little code as necessary. It's the "Command
|
|
||||||
Line Interface Creation Kit". It's highly configurable but comes with
|
|
||||||
sensible defaults out of the box.
|
|
||||||
|
|
||||||
It aims to make the process of writing command line tools quick and fun
|
|
||||||
while also preventing any frustration caused by the inability to
|
|
||||||
implement an intended CLI API.
|
|
||||||
|
|
||||||
Click in three points:
|
|
||||||
|
|
||||||
- Arbitrary nesting of commands
|
|
||||||
- Automatic help page generation
|
|
||||||
- Supports lazy loading of subcommands at runtime
|
|
||||||
|
|
||||||
|
|
||||||
Installing
|
|
||||||
----------
|
|
||||||
|
|
||||||
Install and update using `pip`_:
|
|
||||||
|
|
||||||
.. code-block:: text
|
|
||||||
|
|
||||||
$ pip install -U click
|
|
||||||
|
|
||||||
.. _pip: https://pip.pypa.io/en/stable/quickstart/
|
|
||||||
|
|
||||||
|
|
||||||
A Simple Example
|
|
||||||
----------------
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
import click
|
|
||||||
|
|
||||||
@click.command()
|
|
||||||
@click.option("--count", default=1, help="Number of greetings.")
|
|
||||||
@click.option("--name", prompt="Your name", help="The person to greet.")
|
|
||||||
def hello(count, name):
|
|
||||||
"""Simple program that greets NAME for a total of COUNT times."""
|
|
||||||
for _ in range(count):
|
|
||||||
click.echo(f"Hello, {name}!")
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
hello()
|
|
||||||
|
|
||||||
.. code-block:: text
|
|
||||||
|
|
||||||
$ python hello.py --count=3
|
|
||||||
Your name: Click
|
|
||||||
Hello, Click!
|
|
||||||
Hello, Click!
|
|
||||||
Hello, Click!
|
|
||||||
|
|
||||||
|
|
||||||
Donate
|
|
||||||
------
|
|
||||||
|
|
||||||
The Pallets organization develops and supports Click and other popular
|
|
||||||
packages. In order to grow the community of contributors and users, and
|
|
||||||
allow the maintainers to devote more time to the projects, `please
|
|
||||||
donate today`_.
|
|
||||||
|
|
||||||
.. _please donate today: https://palletsprojects.com/donate
|
|
||||||
|
|
||||||
|
|
||||||
Links
|
|
||||||
-----
|
|
||||||
|
|
||||||
- Documentation: https://click.palletsprojects.com/
|
|
||||||
- Changes: https://click.palletsprojects.com/changes/
|
|
||||||
- PyPI Releases: https://pypi.org/project/click/
|
|
||||||
- Source Code: https://github.com/pallets/click
|
|
||||||
- Issue Tracker: https://github.com/pallets/click/issues
|
|
||||||
- Website: https://palletsprojects.com/p/click
|
|
||||||
- Twitter: https://twitter.com/PalletsTeam
|
|
||||||
- Chat: https://discord.gg/pallets
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
click-8.0.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
|
||||||
click-8.0.1.dist-info/LICENSE.rst,sha256=morRBqOU6FO_4h9C9OctWSgZoigF2ZG18ydQKSkrZY0,1475
|
|
||||||
click-8.0.1.dist-info/METADATA,sha256=Q_8tjC_Ps-9OmIDcovMWvqzrNlmYNwJ7yZxyeJ-SIsk,3216
|
|
||||||
click-8.0.1.dist-info/RECORD,,
|
|
||||||
click-8.0.1.dist-info/WHEEL,sha256=OqRkF0eY5GHssMorFjlbTIq072vpHpF60fIQA6lS9xA,92
|
|
||||||
click-8.0.1.dist-info/top_level.txt,sha256=J1ZQogalYS4pphY_lPECoNMfw0HzTSrZglC4Yfwo4xA,6
|
|
||||||
click/__init__.py,sha256=TweMqq3qEdmxSl3M_O0H1crtKtd7_oS7PDd0WlLote0,3243
|
|
||||||
click/__pycache__/__init__.cpython-39.pyc,,
|
|
||||||
click/__pycache__/_compat.cpython-39.pyc,,
|
|
||||||
click/__pycache__/_termui_impl.cpython-39.pyc,,
|
|
||||||
click/__pycache__/_textwrap.cpython-39.pyc,,
|
|
||||||
click/__pycache__/_unicodefun.cpython-39.pyc,,
|
|
||||||
click/__pycache__/_winconsole.cpython-39.pyc,,
|
|
||||||
click/__pycache__/core.cpython-39.pyc,,
|
|
||||||
click/__pycache__/decorators.cpython-39.pyc,,
|
|
||||||
click/__pycache__/exceptions.cpython-39.pyc,,
|
|
||||||
click/__pycache__/formatting.cpython-39.pyc,,
|
|
||||||
click/__pycache__/globals.cpython-39.pyc,,
|
|
||||||
click/__pycache__/parser.cpython-39.pyc,,
|
|
||||||
click/__pycache__/shell_completion.cpython-39.pyc,,
|
|
||||||
click/__pycache__/termui.cpython-39.pyc,,
|
|
||||||
click/__pycache__/testing.cpython-39.pyc,,
|
|
||||||
click/__pycache__/types.cpython-39.pyc,,
|
|
||||||
click/__pycache__/utils.cpython-39.pyc,,
|
|
||||||
click/_compat.py,sha256=P15KQumAZC2F2MFe_JSRbvVOJcNosQfMDrdZq0ReCLM,18814
|
|
||||||
click/_termui_impl.py,sha256=3IBc-wR8art7cOIN3y4vQ3ftyCs4GNLMjDcrSalUD9c,23423
|
|
||||||
click/_textwrap.py,sha256=10fQ64OcBUMuK7mFvh8363_uoOxPlRItZBmKzRJDgoY,1353
|
|
||||||
click/_unicodefun.py,sha256=JKSh1oSwG_zbjAu4TBCa9tQde2P9FiYcf4MBfy5NdT8,3201
|
|
||||||
click/_winconsole.py,sha256=5ju3jQkcZD0W27WEMGqmEP4y_crUVzPCqsX_FYb7BO0,7860
|
|
||||||
click/core.py,sha256=xYDxID7ShkgY2Lbw7vKOMjP5ImT1NLCTqMJphUicAQ0,111335
|
|
||||||
click/decorators.py,sha256=u_Ehdo3PA2nzCoud9z6fGhxwtMI8vVNG_SL8Bl9lsnY,14871
|
|
||||||
click/exceptions.py,sha256=7gDaLGuFZBeCNwY9ERMsF2-Z3R9Fvq09Zc6IZSKjseo,9167
|
|
||||||
click/formatting.py,sha256=Frf0-5W33-loyY_i9qrwXR8-STnW3m5gvyxLVUdyxyk,9706
|
|
||||||
click/globals.py,sha256=9pcmaNfGS1bJV5DoFYhfv51BPeHP8dWaya7rP3kcrY4,1973
|
|
||||||
click/parser.py,sha256=x5DbnBV9O8kXiMdJAdtpdTO2eRUXw2ab5PRMLxo0po4,19043
|
|
||||||
click/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
||||||
click/shell_completion.py,sha256=F0CHHFOP4ulDsYoqTMm9FXih_OVKsg3mzD-XBzMN79c,17881
|
|
||||||
click/termui.py,sha256=MJNkEntRiNZvwa0z9SVK0d6X9BvUcFhvxKky5M-kBGY,28809
|
|
||||||
click/testing.py,sha256=kLR5Qcny1OlgxaGB3gweTr0gQe1yVlmgQRn2esA2Fz4,16020
|
|
||||||
click/types.py,sha256=ngn3qOaHcDvyeMF2UT5QJNNpJAAVrA9BRj4t8x1xOZM,35375
|
|
||||||
click/utils.py,sha256=q7xUTlebAnIENo2Uv-REArW_erqGFm_8yMW241mMjzQ,18752
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
Wheel-Version: 1.0
|
|
||||||
Generator: bdist_wheel (0.36.2)
|
|
||||||
Root-Is-Purelib: true
|
|
||||||
Tag: py3-none-any
|
|
||||||
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
click
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
"""
|
|
||||||
Click is a simple Python module inspired by the stdlib optparse to make
|
|
||||||
writing command line scripts fun. Unlike other modules, it's based
|
|
||||||
around a simple API that does not come with too much magic and is
|
|
||||||
composable.
|
|
||||||
"""
|
|
||||||
from .core import Argument as Argument
|
|
||||||
from .core import BaseCommand as BaseCommand
|
|
||||||
from .core import Command as Command
|
|
||||||
from .core import CommandCollection as CommandCollection
|
|
||||||
from .core import Context as Context
|
|
||||||
from .core import Group as Group
|
|
||||||
from .core import MultiCommand as MultiCommand
|
|
||||||
from .core import Option as Option
|
|
||||||
from .core import Parameter as Parameter
|
|
||||||
from .decorators import argument as argument
|
|
||||||
from .decorators import command as command
|
|
||||||
from .decorators import confirmation_option as confirmation_option
|
|
||||||
from .decorators import group as group
|
|
||||||
from .decorators import help_option as help_option
|
|
||||||
from .decorators import make_pass_decorator as make_pass_decorator
|
|
||||||
from .decorators import option as option
|
|
||||||
from .decorators import pass_context as pass_context
|
|
||||||
from .decorators import pass_obj as pass_obj
|
|
||||||
from .decorators import password_option as password_option
|
|
||||||
from .decorators import version_option as version_option
|
|
||||||
from .exceptions import Abort as Abort
|
|
||||||
from .exceptions import BadArgumentUsage as BadArgumentUsage
|
|
||||||
from .exceptions import BadOptionUsage as BadOptionUsage
|
|
||||||
from .exceptions import BadParameter as BadParameter
|
|
||||||
from .exceptions import ClickException as ClickException
|
|
||||||
from .exceptions import FileError as FileError
|
|
||||||
from .exceptions import MissingParameter as MissingParameter
|
|
||||||
from .exceptions import NoSuchOption as NoSuchOption
|
|
||||||
from .exceptions import UsageError as UsageError
|
|
||||||
from .formatting import HelpFormatter as HelpFormatter
|
|
||||||
from .formatting import wrap_text as wrap_text
|
|
||||||
from .globals import get_current_context as get_current_context
|
|
||||||
from .parser import OptionParser as OptionParser
|
|
||||||
from .termui import clear as clear
|
|
||||||
from .termui import confirm as confirm
|
|
||||||
from .termui import echo_via_pager as echo_via_pager
|
|
||||||
from .termui import edit as edit
|
|
||||||
from .termui import get_terminal_size as get_terminal_size
|
|
||||||
from .termui import getchar as getchar
|
|
||||||
from .termui import launch as launch
|
|
||||||
from .termui import pause as pause
|
|
||||||
from .termui import progressbar as progressbar
|
|
||||||
from .termui import prompt as prompt
|
|
||||||
from .termui import secho as secho
|
|
||||||
from .termui import style as style
|
|
||||||
from .termui import unstyle as unstyle
|
|
||||||
from .types import BOOL as BOOL
|
|
||||||
from .types import Choice as Choice
|
|
||||||
from .types import DateTime as DateTime
|
|
||||||
from .types import File as File
|
|
||||||
from .types import FLOAT as FLOAT
|
|
||||||
from .types import FloatRange as FloatRange
|
|
||||||
from .types import INT as INT
|
|
||||||
from .types import IntRange as IntRange
|
|
||||||
from .types import ParamType as ParamType
|
|
||||||
from .types import Path as Path
|
|
||||||
from .types import STRING as STRING
|
|
||||||
from .types import Tuple as Tuple
|
|
||||||
from .types import UNPROCESSED as UNPROCESSED
|
|
||||||
from .types import UUID as UUID
|
|
||||||
from .utils import echo as echo
|
|
||||||
from .utils import format_filename as format_filename
|
|
||||||
from .utils import get_app_dir as get_app_dir
|
|
||||||
from .utils import get_binary_stream as get_binary_stream
|
|
||||||
from .utils import get_os_args as get_os_args
|
|
||||||
from .utils import get_text_stream as get_text_stream
|
|
||||||
from .utils import open_file as open_file
|
|
||||||
|
|
||||||
__version__ = "8.0.1"
|
|
||||||
@@ -1,627 +0,0 @@
|
|||||||
import codecs
|
|
||||||
import io
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
import typing as t
|
|
||||||
from weakref import WeakKeyDictionary
|
|
||||||
|
|
||||||
CYGWIN = sys.platform.startswith("cygwin")
|
|
||||||
MSYS2 = sys.platform.startswith("win") and ("GCC" in sys.version)
|
|
||||||
# Determine local App Engine environment, per Google's own suggestion
|
|
||||||
APP_ENGINE = "APPENGINE_RUNTIME" in os.environ and "Development/" in os.environ.get(
|
|
||||||
"SERVER_SOFTWARE", ""
|
|
||||||
)
|
|
||||||
WIN = sys.platform.startswith("win") and not APP_ENGINE and not MSYS2
|
|
||||||
auto_wrap_for_ansi: t.Optional[t.Callable[[t.TextIO], t.TextIO]] = None
|
|
||||||
_ansi_re = re.compile(r"\033\[[;?0-9]*[a-zA-Z]")
|
|
||||||
|
|
||||||
|
|
||||||
def get_filesystem_encoding() -> str:
|
|
||||||
return sys.getfilesystemencoding() or sys.getdefaultencoding()
|
|
||||||
|
|
||||||
|
|
||||||
def _make_text_stream(
|
|
||||||
stream: t.BinaryIO,
|
|
||||||
encoding: t.Optional[str],
|
|
||||||
errors: t.Optional[str],
|
|
||||||
force_readable: bool = False,
|
|
||||||
force_writable: bool = False,
|
|
||||||
) -> t.TextIO:
|
|
||||||
if encoding is None:
|
|
||||||
encoding = get_best_encoding(stream)
|
|
||||||
if errors is None:
|
|
||||||
errors = "replace"
|
|
||||||
return _NonClosingTextIOWrapper(
|
|
||||||
stream,
|
|
||||||
encoding,
|
|
||||||
errors,
|
|
||||||
line_buffering=True,
|
|
||||||
force_readable=force_readable,
|
|
||||||
force_writable=force_writable,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def is_ascii_encoding(encoding: str) -> bool:
|
|
||||||
"""Checks if a given encoding is ascii."""
|
|
||||||
try:
|
|
||||||
return codecs.lookup(encoding).name == "ascii"
|
|
||||||
except LookupError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def get_best_encoding(stream: t.IO) -> str:
|
|
||||||
"""Returns the default stream encoding if not found."""
|
|
||||||
rv = getattr(stream, "encoding", None) or sys.getdefaultencoding()
|
|
||||||
if is_ascii_encoding(rv):
|
|
||||||
return "utf-8"
|
|
||||||
return rv
|
|
||||||
|
|
||||||
|
|
||||||
class _NonClosingTextIOWrapper(io.TextIOWrapper):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
stream: t.BinaryIO,
|
|
||||||
encoding: t.Optional[str],
|
|
||||||
errors: t.Optional[str],
|
|
||||||
force_readable: bool = False,
|
|
||||||
force_writable: bool = False,
|
|
||||||
**extra: t.Any,
|
|
||||||
) -> None:
|
|
||||||
self._stream = stream = t.cast(
|
|
||||||
t.BinaryIO, _FixupStream(stream, force_readable, force_writable)
|
|
||||||
)
|
|
||||||
super().__init__(stream, encoding, errors, **extra)
|
|
||||||
|
|
||||||
def __del__(self) -> None:
|
|
||||||
try:
|
|
||||||
self.detach()
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def isatty(self) -> bool:
|
|
||||||
# https://bitbucket.org/pypy/pypy/issue/1803
|
|
||||||
return self._stream.isatty()
|
|
||||||
|
|
||||||
|
|
||||||
class _FixupStream:
|
|
||||||
"""The new io interface needs more from streams than streams
|
|
||||||
traditionally implement. As such, this fix-up code is necessary in
|
|
||||||
some circumstances.
|
|
||||||
|
|
||||||
The forcing of readable and writable flags are there because some tools
|
|
||||||
put badly patched objects on sys (one such offender are certain version
|
|
||||||
of jupyter notebook).
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
stream: t.BinaryIO,
|
|
||||||
force_readable: bool = False,
|
|
||||||
force_writable: bool = False,
|
|
||||||
):
|
|
||||||
self._stream = stream
|
|
||||||
self._force_readable = force_readable
|
|
||||||
self._force_writable = force_writable
|
|
||||||
|
|
||||||
def __getattr__(self, name: str) -> t.Any:
|
|
||||||
return getattr(self._stream, name)
|
|
||||||
|
|
||||||
def read1(self, size: int) -> bytes:
|
|
||||||
f = getattr(self._stream, "read1", None)
|
|
||||||
|
|
||||||
if f is not None:
|
|
||||||
return t.cast(bytes, f(size))
|
|
||||||
|
|
||||||
return self._stream.read(size)
|
|
||||||
|
|
||||||
def readable(self) -> bool:
|
|
||||||
if self._force_readable:
|
|
||||||
return True
|
|
||||||
x = getattr(self._stream, "readable", None)
|
|
||||||
if x is not None:
|
|
||||||
return t.cast(bool, x())
|
|
||||||
try:
|
|
||||||
self._stream.read(0)
|
|
||||||
except Exception:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def writable(self) -> bool:
|
|
||||||
if self._force_writable:
|
|
||||||
return True
|
|
||||||
x = getattr(self._stream, "writable", None)
|
|
||||||
if x is not None:
|
|
||||||
return t.cast(bool, x())
|
|
||||||
try:
|
|
||||||
self._stream.write("") # type: ignore
|
|
||||||
except Exception:
|
|
||||||
try:
|
|
||||||
self._stream.write(b"")
|
|
||||||
except Exception:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def seekable(self) -> bool:
|
|
||||||
x = getattr(self._stream, "seekable", None)
|
|
||||||
if x is not None:
|
|
||||||
return t.cast(bool, x())
|
|
||||||
try:
|
|
||||||
self._stream.seek(self._stream.tell())
|
|
||||||
except Exception:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def _is_binary_reader(stream: t.IO, default: bool = False) -> bool:
|
|
||||||
try:
|
|
||||||
return isinstance(stream.read(0), bytes)
|
|
||||||
except Exception:
|
|
||||||
return default
|
|
||||||
# This happens in some cases where the stream was already
|
|
||||||
# closed. In this case, we assume the default.
|
|
||||||
|
|
||||||
|
|
||||||
def _is_binary_writer(stream: t.IO, default: bool = False) -> bool:
|
|
||||||
try:
|
|
||||||
stream.write(b"")
|
|
||||||
except Exception:
|
|
||||||
try:
|
|
||||||
stream.write("")
|
|
||||||
return False
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
return default
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def _find_binary_reader(stream: t.IO) -> t.Optional[t.BinaryIO]:
|
|
||||||
# We need to figure out if the given stream is already binary.
|
|
||||||
# This can happen because the official docs recommend detaching
|
|
||||||
# the streams to get binary streams. Some code might do this, so
|
|
||||||
# we need to deal with this case explicitly.
|
|
||||||
if _is_binary_reader(stream, False):
|
|
||||||
return t.cast(t.BinaryIO, stream)
|
|
||||||
|
|
||||||
buf = getattr(stream, "buffer", None)
|
|
||||||
|
|
||||||
# Same situation here; this time we assume that the buffer is
|
|
||||||
# actually binary in case it's closed.
|
|
||||||
if buf is not None and _is_binary_reader(buf, True):
|
|
||||||
return t.cast(t.BinaryIO, buf)
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def _find_binary_writer(stream: t.IO) -> t.Optional[t.BinaryIO]:
|
|
||||||
# We need to figure out if the given stream is already binary.
|
|
||||||
# This can happen because the official docs recommend detaching
|
|
||||||
# the streams to get binary streams. Some code might do this, so
|
|
||||||
# we need to deal with this case explicitly.
|
|
||||||
if _is_binary_writer(stream, False):
|
|
||||||
return t.cast(t.BinaryIO, stream)
|
|
||||||
|
|
||||||
buf = getattr(stream, "buffer", None)
|
|
||||||
|
|
||||||
# Same situation here; this time we assume that the buffer is
|
|
||||||
# actually binary in case it's closed.
|
|
||||||
if buf is not None and _is_binary_writer(buf, True):
|
|
||||||
return t.cast(t.BinaryIO, buf)
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def _stream_is_misconfigured(stream: t.TextIO) -> bool:
|
|
||||||
"""A stream is misconfigured if its encoding is ASCII."""
|
|
||||||
# If the stream does not have an encoding set, we assume it's set
|
|
||||||
# to ASCII. This appears to happen in certain unittest
|
|
||||||
# environments. It's not quite clear what the correct behavior is
|
|
||||||
# but this at least will force Click to recover somehow.
|
|
||||||
return is_ascii_encoding(getattr(stream, "encoding", None) or "ascii")
|
|
||||||
|
|
||||||
|
|
||||||
def _is_compat_stream_attr(stream: t.TextIO, attr: str, value: t.Optional[str]) -> bool:
|
|
||||||
"""A stream attribute is compatible if it is equal to the
|
|
||||||
desired value or the desired value is unset and the attribute
|
|
||||||
has a value.
|
|
||||||
"""
|
|
||||||
stream_value = getattr(stream, attr, None)
|
|
||||||
return stream_value == value or (value is None and stream_value is not None)
|
|
||||||
|
|
||||||
|
|
||||||
def _is_compatible_text_stream(
|
|
||||||
stream: t.TextIO, encoding: t.Optional[str], errors: t.Optional[str]
|
|
||||||
) -> bool:
|
|
||||||
"""Check if a stream's encoding and errors attributes are
|
|
||||||
compatible with the desired values.
|
|
||||||
"""
|
|
||||||
return _is_compat_stream_attr(
|
|
||||||
stream, "encoding", encoding
|
|
||||||
) and _is_compat_stream_attr(stream, "errors", errors)
|
|
||||||
|
|
||||||
|
|
||||||
def _force_correct_text_stream(
|
|
||||||
text_stream: t.IO,
|
|
||||||
encoding: t.Optional[str],
|
|
||||||
errors: t.Optional[str],
|
|
||||||
is_binary: t.Callable[[t.IO, bool], bool],
|
|
||||||
find_binary: t.Callable[[t.IO], t.Optional[t.BinaryIO]],
|
|
||||||
force_readable: bool = False,
|
|
||||||
force_writable: bool = False,
|
|
||||||
) -> t.TextIO:
|
|
||||||
if is_binary(text_stream, False):
|
|
||||||
binary_reader = t.cast(t.BinaryIO, text_stream)
|
|
||||||
else:
|
|
||||||
text_stream = t.cast(t.TextIO, text_stream)
|
|
||||||
# If the stream looks compatible, and won't default to a
|
|
||||||
# misconfigured ascii encoding, return it as-is.
|
|
||||||
if _is_compatible_text_stream(text_stream, encoding, errors) and not (
|
|
||||||
encoding is None and _stream_is_misconfigured(text_stream)
|
|
||||||
):
|
|
||||||
return text_stream
|
|
||||||
|
|
||||||
# Otherwise, get the underlying binary reader.
|
|
||||||
possible_binary_reader = find_binary(text_stream)
|
|
||||||
|
|
||||||
# If that's not possible, silently use the original reader
|
|
||||||
# and get mojibake instead of exceptions.
|
|
||||||
if possible_binary_reader is None:
|
|
||||||
return text_stream
|
|
||||||
|
|
||||||
binary_reader = possible_binary_reader
|
|
||||||
|
|
||||||
# Default errors to replace instead of strict in order to get
|
|
||||||
# something that works.
|
|
||||||
if errors is None:
|
|
||||||
errors = "replace"
|
|
||||||
|
|
||||||
# Wrap the binary stream in a text stream with the correct
|
|
||||||
# encoding parameters.
|
|
||||||
return _make_text_stream(
|
|
||||||
binary_reader,
|
|
||||||
encoding,
|
|
||||||
errors,
|
|
||||||
force_readable=force_readable,
|
|
||||||
force_writable=force_writable,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _force_correct_text_reader(
|
|
||||||
text_reader: t.IO,
|
|
||||||
encoding: t.Optional[str],
|
|
||||||
errors: t.Optional[str],
|
|
||||||
force_readable: bool = False,
|
|
||||||
) -> t.TextIO:
|
|
||||||
return _force_correct_text_stream(
|
|
||||||
text_reader,
|
|
||||||
encoding,
|
|
||||||
errors,
|
|
||||||
_is_binary_reader,
|
|
||||||
_find_binary_reader,
|
|
||||||
force_readable=force_readable,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _force_correct_text_writer(
|
|
||||||
text_writer: t.IO,
|
|
||||||
encoding: t.Optional[str],
|
|
||||||
errors: t.Optional[str],
|
|
||||||
force_writable: bool = False,
|
|
||||||
) -> t.TextIO:
|
|
||||||
return _force_correct_text_stream(
|
|
||||||
text_writer,
|
|
||||||
encoding,
|
|
||||||
errors,
|
|
||||||
_is_binary_writer,
|
|
||||||
_find_binary_writer,
|
|
||||||
force_writable=force_writable,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_binary_stdin() -> t.BinaryIO:
|
|
||||||
reader = _find_binary_reader(sys.stdin)
|
|
||||||
if reader is None:
|
|
||||||
raise RuntimeError("Was not able to determine binary stream for sys.stdin.")
|
|
||||||
return reader
|
|
||||||
|
|
||||||
|
|
||||||
def get_binary_stdout() -> t.BinaryIO:
|
|
||||||
writer = _find_binary_writer(sys.stdout)
|
|
||||||
if writer is None:
|
|
||||||
raise RuntimeError("Was not able to determine binary stream for sys.stdout.")
|
|
||||||
return writer
|
|
||||||
|
|
||||||
|
|
||||||
def get_binary_stderr() -> t.BinaryIO:
|
|
||||||
writer = _find_binary_writer(sys.stderr)
|
|
||||||
if writer is None:
|
|
||||||
raise RuntimeError("Was not able to determine binary stream for sys.stderr.")
|
|
||||||
return writer
|
|
||||||
|
|
||||||
|
|
||||||
def get_text_stdin(
|
|
||||||
encoding: t.Optional[str] = None, errors: t.Optional[str] = None
|
|
||||||
) -> t.TextIO:
|
|
||||||
rv = _get_windows_console_stream(sys.stdin, encoding, errors)
|
|
||||||
if rv is not None:
|
|
||||||
return rv
|
|
||||||
return _force_correct_text_reader(sys.stdin, encoding, errors, force_readable=True)
|
|
||||||
|
|
||||||
|
|
||||||
def get_text_stdout(
|
|
||||||
encoding: t.Optional[str] = None, errors: t.Optional[str] = None
|
|
||||||
) -> t.TextIO:
|
|
||||||
rv = _get_windows_console_stream(sys.stdout, encoding, errors)
|
|
||||||
if rv is not None:
|
|
||||||
return rv
|
|
||||||
return _force_correct_text_writer(sys.stdout, encoding, errors, force_writable=True)
|
|
||||||
|
|
||||||
|
|
||||||
def get_text_stderr(
|
|
||||||
encoding: t.Optional[str] = None, errors: t.Optional[str] = None
|
|
||||||
) -> t.TextIO:
|
|
||||||
rv = _get_windows_console_stream(sys.stderr, encoding, errors)
|
|
||||||
if rv is not None:
|
|
||||||
return rv
|
|
||||||
return _force_correct_text_writer(sys.stderr, encoding, errors, force_writable=True)
|
|
||||||
|
|
||||||
|
|
||||||
def _wrap_io_open(
|
|
||||||
file: t.Union[str, os.PathLike, int],
|
|
||||||
mode: str,
|
|
||||||
encoding: t.Optional[str],
|
|
||||||
errors: t.Optional[str],
|
|
||||||
) -> t.IO:
|
|
||||||
"""Handles not passing ``encoding`` and ``errors`` in binary mode."""
|
|
||||||
if "b" in mode:
|
|
||||||
return open(file, mode)
|
|
||||||
|
|
||||||
return open(file, mode, encoding=encoding, errors=errors)
|
|
||||||
|
|
||||||
|
|
||||||
def open_stream(
|
|
||||||
filename: str,
|
|
||||||
mode: str = "r",
|
|
||||||
encoding: t.Optional[str] = None,
|
|
||||||
errors: t.Optional[str] = "strict",
|
|
||||||
atomic: bool = False,
|
|
||||||
) -> t.Tuple[t.IO, bool]:
|
|
||||||
binary = "b" in mode
|
|
||||||
|
|
||||||
# Standard streams first. These are simple because they don't need
|
|
||||||
# special handling for the atomic flag. It's entirely ignored.
|
|
||||||
if filename == "-":
|
|
||||||
if any(m in mode for m in ["w", "a", "x"]):
|
|
||||||
if binary:
|
|
||||||
return get_binary_stdout(), False
|
|
||||||
return get_text_stdout(encoding=encoding, errors=errors), False
|
|
||||||
if binary:
|
|
||||||
return get_binary_stdin(), False
|
|
||||||
return get_text_stdin(encoding=encoding, errors=errors), False
|
|
||||||
|
|
||||||
# Non-atomic writes directly go out through the regular open functions.
|
|
||||||
if not atomic:
|
|
||||||
return _wrap_io_open(filename, mode, encoding, errors), True
|
|
||||||
|
|
||||||
# Some usability stuff for atomic writes
|
|
||||||
if "a" in mode:
|
|
||||||
raise ValueError(
|
|
||||||
"Appending to an existing file is not supported, because that"
|
|
||||||
" would involve an expensive `copy`-operation to a temporary"
|
|
||||||
" file. Open the file in normal `w`-mode and copy explicitly"
|
|
||||||
" if that's what you're after."
|
|
||||||
)
|
|
||||||
if "x" in mode:
|
|
||||||
raise ValueError("Use the `overwrite`-parameter instead.")
|
|
||||||
if "w" not in mode:
|
|
||||||
raise ValueError("Atomic writes only make sense with `w`-mode.")
|
|
||||||
|
|
||||||
# Atomic writes are more complicated. They work by opening a file
|
|
||||||
# as a proxy in the same folder and then using the fdopen
|
|
||||||
# functionality to wrap it in a Python file. Then we wrap it in an
|
|
||||||
# atomic file that moves the file over on close.
|
|
||||||
import errno
|
|
||||||
import random
|
|
||||||
|
|
||||||
try:
|
|
||||||
perm: t.Optional[int] = os.stat(filename).st_mode
|
|
||||||
except OSError:
|
|
||||||
perm = None
|
|
||||||
|
|
||||||
flags = os.O_RDWR | os.O_CREAT | os.O_EXCL
|
|
||||||
|
|
||||||
if binary:
|
|
||||||
flags |= getattr(os, "O_BINARY", 0)
|
|
||||||
|
|
||||||
while True:
|
|
||||||
tmp_filename = os.path.join(
|
|
||||||
os.path.dirname(filename),
|
|
||||||
f".__atomic-write{random.randrange(1 << 32):08x}",
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
fd = os.open(tmp_filename, flags, 0o666 if perm is None else perm)
|
|
||||||
break
|
|
||||||
except OSError as e:
|
|
||||||
if e.errno == errno.EEXIST or (
|
|
||||||
os.name == "nt"
|
|
||||||
and e.errno == errno.EACCES
|
|
||||||
and os.path.isdir(e.filename)
|
|
||||||
and os.access(e.filename, os.W_OK)
|
|
||||||
):
|
|
||||||
continue
|
|
||||||
raise
|
|
||||||
|
|
||||||
if perm is not None:
|
|
||||||
os.chmod(tmp_filename, perm) # in case perm includes bits in umask
|
|
||||||
|
|
||||||
f = _wrap_io_open(fd, mode, encoding, errors)
|
|
||||||
af = _AtomicFile(f, tmp_filename, os.path.realpath(filename))
|
|
||||||
return t.cast(t.IO, af), True
|
|
||||||
|
|
||||||
|
|
||||||
class _AtomicFile:
|
|
||||||
def __init__(self, f: t.IO, tmp_filename: str, real_filename: str) -> None:
|
|
||||||
self._f = f
|
|
||||||
self._tmp_filename = tmp_filename
|
|
||||||
self._real_filename = real_filename
|
|
||||||
self.closed = False
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self) -> str:
|
|
||||||
return self._real_filename
|
|
||||||
|
|
||||||
def close(self, delete: bool = False) -> None:
|
|
||||||
if self.closed:
|
|
||||||
return
|
|
||||||
self._f.close()
|
|
||||||
os.replace(self._tmp_filename, self._real_filename)
|
|
||||||
self.closed = True
|
|
||||||
|
|
||||||
def __getattr__(self, name: str) -> t.Any:
|
|
||||||
return getattr(self._f, name)
|
|
||||||
|
|
||||||
def __enter__(self) -> "_AtomicFile":
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_value, tb): # type: ignore
|
|
||||||
self.close(delete=exc_type is not None)
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
|
||||||
return repr(self._f)
|
|
||||||
|
|
||||||
|
|
||||||
def strip_ansi(value: str) -> str:
|
|
||||||
return _ansi_re.sub("", value)
|
|
||||||
|
|
||||||
|
|
||||||
def _is_jupyter_kernel_output(stream: t.IO) -> bool:
|
|
||||||
while isinstance(stream, (_FixupStream, _NonClosingTextIOWrapper)):
|
|
||||||
stream = stream._stream
|
|
||||||
|
|
||||||
return stream.__class__.__module__.startswith("ipykernel.")
|
|
||||||
|
|
||||||
|
|
||||||
def should_strip_ansi(
|
|
||||||
stream: t.Optional[t.IO] = None, color: t.Optional[bool] = None
|
|
||||||
) -> bool:
|
|
||||||
if color is None:
|
|
||||||
if stream is None:
|
|
||||||
stream = sys.stdin
|
|
||||||
return not isatty(stream) and not _is_jupyter_kernel_output(stream)
|
|
||||||
return not color
|
|
||||||
|
|
||||||
|
|
||||||
# On Windows, wrap the output streams with colorama to support ANSI
|
|
||||||
# color codes.
|
|
||||||
# NOTE: double check is needed so mypy does not analyze this on Linux
|
|
||||||
if sys.platform.startswith("win") and WIN:
|
|
||||||
from ._winconsole import _get_windows_console_stream
|
|
||||||
|
|
||||||
def _get_argv_encoding() -> str:
|
|
||||||
import locale
|
|
||||||
|
|
||||||
return locale.getpreferredencoding()
|
|
||||||
|
|
||||||
_ansi_stream_wrappers: t.MutableMapping[t.TextIO, t.TextIO] = WeakKeyDictionary()
|
|
||||||
|
|
||||||
def auto_wrap_for_ansi(
|
|
||||||
stream: t.TextIO, color: t.Optional[bool] = None
|
|
||||||
) -> t.TextIO:
|
|
||||||
"""Support ANSI color and style codes on Windows by wrapping a
|
|
||||||
stream with colorama.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
cached = _ansi_stream_wrappers.get(stream)
|
|
||||||
except Exception:
|
|
||||||
cached = None
|
|
||||||
|
|
||||||
if cached is not None:
|
|
||||||
return cached
|
|
||||||
|
|
||||||
import colorama
|
|
||||||
|
|
||||||
strip = should_strip_ansi(stream, color)
|
|
||||||
ansi_wrapper = colorama.AnsiToWin32(stream, strip=strip)
|
|
||||||
rv = t.cast(t.TextIO, ansi_wrapper.stream)
|
|
||||||
_write = rv.write
|
|
||||||
|
|
||||||
def _safe_write(s):
|
|
||||||
try:
|
|
||||||
return _write(s)
|
|
||||||
except BaseException:
|
|
||||||
ansi_wrapper.reset_all()
|
|
||||||
raise
|
|
||||||
|
|
||||||
rv.write = _safe_write
|
|
||||||
|
|
||||||
try:
|
|
||||||
_ansi_stream_wrappers[stream] = rv
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return rv
|
|
||||||
|
|
||||||
|
|
||||||
else:
|
|
||||||
|
|
||||||
def _get_argv_encoding() -> str:
|
|
||||||
return getattr(sys.stdin, "encoding", None) or get_filesystem_encoding()
|
|
||||||
|
|
||||||
def _get_windows_console_stream(
|
|
||||||
f: t.TextIO, encoding: t.Optional[str], errors: t.Optional[str]
|
|
||||||
) -> t.Optional[t.TextIO]:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def term_len(x: str) -> int:
|
|
||||||
return len(strip_ansi(x))
|
|
||||||
|
|
||||||
|
|
||||||
def isatty(stream: t.IO) -> bool:
|
|
||||||
try:
|
|
||||||
return stream.isatty()
|
|
||||||
except Exception:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def _make_cached_stream_func(
|
|
||||||
src_func: t.Callable[[], t.TextIO], wrapper_func: t.Callable[[], t.TextIO]
|
|
||||||
) -> t.Callable[[], t.TextIO]:
|
|
||||||
cache: t.MutableMapping[t.TextIO, t.TextIO] = WeakKeyDictionary()
|
|
||||||
|
|
||||||
def func() -> t.TextIO:
|
|
||||||
stream = src_func()
|
|
||||||
try:
|
|
||||||
rv = cache.get(stream)
|
|
||||||
except Exception:
|
|
||||||
rv = None
|
|
||||||
if rv is not None:
|
|
||||||
return rv
|
|
||||||
rv = wrapper_func()
|
|
||||||
try:
|
|
||||||
cache[stream] = rv
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
return rv
|
|
||||||
|
|
||||||
return func
|
|
||||||
|
|
||||||
|
|
||||||
_default_text_stdin = _make_cached_stream_func(lambda: sys.stdin, get_text_stdin)
|
|
||||||
_default_text_stdout = _make_cached_stream_func(lambda: sys.stdout, get_text_stdout)
|
|
||||||
_default_text_stderr = _make_cached_stream_func(lambda: sys.stderr, get_text_stderr)
|
|
||||||
|
|
||||||
|
|
||||||
binary_streams: t.Mapping[str, t.Callable[[], t.BinaryIO]] = {
|
|
||||||
"stdin": get_binary_stdin,
|
|
||||||
"stdout": get_binary_stdout,
|
|
||||||
"stderr": get_binary_stderr,
|
|
||||||
}
|
|
||||||
|
|
||||||
text_streams: t.Mapping[
|
|
||||||
str, t.Callable[[t.Optional[str], t.Optional[str]], t.TextIO]
|
|
||||||
] = {
|
|
||||||
"stdin": get_text_stdin,
|
|
||||||
"stdout": get_text_stdout,
|
|
||||||
"stderr": get_text_stderr,
|
|
||||||
}
|
|
||||||
@@ -1,717 +0,0 @@
|
|||||||
"""
|
|
||||||
This module contains implementations for the termui module. To keep the
|
|
||||||
import time of Click down, some infrequently used functionality is
|
|
||||||
placed in this module and only imported as needed.
|
|
||||||
"""
|
|
||||||
import contextlib
|
|
||||||
import math
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
import typing as t
|
|
||||||
from gettext import gettext as _
|
|
||||||
|
|
||||||
from ._compat import _default_text_stdout
|
|
||||||
from ._compat import CYGWIN
|
|
||||||
from ._compat import get_best_encoding
|
|
||||||
from ._compat import isatty
|
|
||||||
from ._compat import open_stream
|
|
||||||
from ._compat import strip_ansi
|
|
||||||
from ._compat import term_len
|
|
||||||
from ._compat import WIN
|
|
||||||
from .exceptions import ClickException
|
|
||||||
from .utils import echo
|
|
||||||
|
|
||||||
V = t.TypeVar("V")
|
|
||||||
|
|
||||||
if os.name == "nt":
|
|
||||||
BEFORE_BAR = "\r"
|
|
||||||
AFTER_BAR = "\n"
|
|
||||||
else:
|
|
||||||
BEFORE_BAR = "\r\033[?25l"
|
|
||||||
AFTER_BAR = "\033[?25h\n"
|
|
||||||
|
|
||||||
|
|
||||||
class ProgressBar(t.Generic[V]):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
iterable: t.Optional[t.Iterable[V]],
|
|
||||||
length: t.Optional[int] = None,
|
|
||||||
fill_char: str = "#",
|
|
||||||
empty_char: str = " ",
|
|
||||||
bar_template: str = "%(bar)s",
|
|
||||||
info_sep: str = " ",
|
|
||||||
show_eta: bool = True,
|
|
||||||
show_percent: t.Optional[bool] = None,
|
|
||||||
show_pos: bool = False,
|
|
||||||
item_show_func: t.Optional[t.Callable[[t.Optional[V]], t.Optional[str]]] = None,
|
|
||||||
label: t.Optional[str] = None,
|
|
||||||
file: t.Optional[t.TextIO] = None,
|
|
||||||
color: t.Optional[bool] = None,
|
|
||||||
update_min_steps: int = 1,
|
|
||||||
width: int = 30,
|
|
||||||
) -> None:
|
|
||||||
self.fill_char = fill_char
|
|
||||||
self.empty_char = empty_char
|
|
||||||
self.bar_template = bar_template
|
|
||||||
self.info_sep = info_sep
|
|
||||||
self.show_eta = show_eta
|
|
||||||
self.show_percent = show_percent
|
|
||||||
self.show_pos = show_pos
|
|
||||||
self.item_show_func = item_show_func
|
|
||||||
self.label = label or ""
|
|
||||||
if file is None:
|
|
||||||
file = _default_text_stdout()
|
|
||||||
self.file = file
|
|
||||||
self.color = color
|
|
||||||
self.update_min_steps = update_min_steps
|
|
||||||
self._completed_intervals = 0
|
|
||||||
self.width = width
|
|
||||||
self.autowidth = width == 0
|
|
||||||
|
|
||||||
if length is None:
|
|
||||||
from operator import length_hint
|
|
||||||
|
|
||||||
length = length_hint(iterable, -1)
|
|
||||||
|
|
||||||
if length == -1:
|
|
||||||
length = None
|
|
||||||
if iterable is None:
|
|
||||||
if length is None:
|
|
||||||
raise TypeError("iterable or length is required")
|
|
||||||
iterable = t.cast(t.Iterable[V], range(length))
|
|
||||||
self.iter = iter(iterable)
|
|
||||||
self.length = length
|
|
||||||
self.pos = 0
|
|
||||||
self.avg: t.List[float] = []
|
|
||||||
self.start = self.last_eta = time.time()
|
|
||||||
self.eta_known = False
|
|
||||||
self.finished = False
|
|
||||||
self.max_width: t.Optional[int] = None
|
|
||||||
self.entered = False
|
|
||||||
self.current_item: t.Optional[V] = None
|
|
||||||
self.is_hidden = not isatty(self.file)
|
|
||||||
self._last_line: t.Optional[str] = None
|
|
||||||
|
|
||||||
def __enter__(self) -> "ProgressBar":
|
|
||||||
self.entered = True
|
|
||||||
self.render_progress()
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_value, tb): # type: ignore
|
|
||||||
self.render_finish()
|
|
||||||
|
|
||||||
def __iter__(self) -> t.Iterator[V]:
|
|
||||||
if not self.entered:
|
|
||||||
raise RuntimeError("You need to use progress bars in a with block.")
|
|
||||||
self.render_progress()
|
|
||||||
return self.generator()
|
|
||||||
|
|
||||||
def __next__(self) -> V:
|
|
||||||
# Iteration is defined in terms of a generator function,
|
|
||||||
# returned by iter(self); use that to define next(). This works
|
|
||||||
# because `self.iter` is an iterable consumed by that generator,
|
|
||||||
# so it is re-entry safe. Calling `next(self.generator())`
|
|
||||||
# twice works and does "what you want".
|
|
||||||
return next(iter(self))
|
|
||||||
|
|
||||||
def render_finish(self) -> None:
|
|
||||||
if self.is_hidden:
|
|
||||||
return
|
|
||||||
self.file.write(AFTER_BAR)
|
|
||||||
self.file.flush()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def pct(self) -> float:
|
|
||||||
if self.finished:
|
|
||||||
return 1.0
|
|
||||||
return min(self.pos / (float(self.length or 1) or 1), 1.0)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def time_per_iteration(self) -> float:
|
|
||||||
if not self.avg:
|
|
||||||
return 0.0
|
|
||||||
return sum(self.avg) / float(len(self.avg))
|
|
||||||
|
|
||||||
@property
|
|
||||||
def eta(self) -> float:
|
|
||||||
if self.length is not None and not self.finished:
|
|
||||||
return self.time_per_iteration * (self.length - self.pos)
|
|
||||||
return 0.0
|
|
||||||
|
|
||||||
def format_eta(self) -> str:
|
|
||||||
if self.eta_known:
|
|
||||||
t = int(self.eta)
|
|
||||||
seconds = t % 60
|
|
||||||
t //= 60
|
|
||||||
minutes = t % 60
|
|
||||||
t //= 60
|
|
||||||
hours = t % 24
|
|
||||||
t //= 24
|
|
||||||
if t > 0:
|
|
||||||
return f"{t}d {hours:02}:{minutes:02}:{seconds:02}"
|
|
||||||
else:
|
|
||||||
return f"{hours:02}:{minutes:02}:{seconds:02}"
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def format_pos(self) -> str:
|
|
||||||
pos = str(self.pos)
|
|
||||||
if self.length is not None:
|
|
||||||
pos += f"/{self.length}"
|
|
||||||
return pos
|
|
||||||
|
|
||||||
def format_pct(self) -> str:
|
|
||||||
return f"{int(self.pct * 100): 4}%"[1:]
|
|
||||||
|
|
||||||
def format_bar(self) -> str:
|
|
||||||
if self.length is not None:
|
|
||||||
bar_length = int(self.pct * self.width)
|
|
||||||
bar = self.fill_char * bar_length
|
|
||||||
bar += self.empty_char * (self.width - bar_length)
|
|
||||||
elif self.finished:
|
|
||||||
bar = self.fill_char * self.width
|
|
||||||
else:
|
|
||||||
chars = list(self.empty_char * (self.width or 1))
|
|
||||||
if self.time_per_iteration != 0:
|
|
||||||
chars[
|
|
||||||
int(
|
|
||||||
(math.cos(self.pos * self.time_per_iteration) / 2.0 + 0.5)
|
|
||||||
* self.width
|
|
||||||
)
|
|
||||||
] = self.fill_char
|
|
||||||
bar = "".join(chars)
|
|
||||||
return bar
|
|
||||||
|
|
||||||
def format_progress_line(self) -> str:
|
|
||||||
show_percent = self.show_percent
|
|
||||||
|
|
||||||
info_bits = []
|
|
||||||
if self.length is not None and show_percent is None:
|
|
||||||
show_percent = not self.show_pos
|
|
||||||
|
|
||||||
if self.show_pos:
|
|
||||||
info_bits.append(self.format_pos())
|
|
||||||
if show_percent:
|
|
||||||
info_bits.append(self.format_pct())
|
|
||||||
if self.show_eta and self.eta_known and not self.finished:
|
|
||||||
info_bits.append(self.format_eta())
|
|
||||||
if self.item_show_func is not None:
|
|
||||||
item_info = self.item_show_func(self.current_item)
|
|
||||||
if item_info is not None:
|
|
||||||
info_bits.append(item_info)
|
|
||||||
|
|
||||||
return (
|
|
||||||
self.bar_template
|
|
||||||
% {
|
|
||||||
"label": self.label,
|
|
||||||
"bar": self.format_bar(),
|
|
||||||
"info": self.info_sep.join(info_bits),
|
|
||||||
}
|
|
||||||
).rstrip()
|
|
||||||
|
|
||||||
def render_progress(self) -> None:
|
|
||||||
import shutil
|
|
||||||
|
|
||||||
if self.is_hidden:
|
|
||||||
# Only output the label as it changes if the output is not a
|
|
||||||
# TTY. Use file=stderr if you expect to be piping stdout.
|
|
||||||
if self._last_line != self.label:
|
|
||||||
self._last_line = self.label
|
|
||||||
echo(self.label, file=self.file, color=self.color)
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
buf = []
|
|
||||||
# Update width in case the terminal has been resized
|
|
||||||
if self.autowidth:
|
|
||||||
old_width = self.width
|
|
||||||
self.width = 0
|
|
||||||
clutter_length = term_len(self.format_progress_line())
|
|
||||||
new_width = max(0, shutil.get_terminal_size().columns - clutter_length)
|
|
||||||
if new_width < old_width:
|
|
||||||
buf.append(BEFORE_BAR)
|
|
||||||
buf.append(" " * self.max_width) # type: ignore
|
|
||||||
self.max_width = new_width
|
|
||||||
self.width = new_width
|
|
||||||
|
|
||||||
clear_width = self.width
|
|
||||||
if self.max_width is not None:
|
|
||||||
clear_width = self.max_width
|
|
||||||
|
|
||||||
buf.append(BEFORE_BAR)
|
|
||||||
line = self.format_progress_line()
|
|
||||||
line_len = term_len(line)
|
|
||||||
if self.max_width is None or self.max_width < line_len:
|
|
||||||
self.max_width = line_len
|
|
||||||
|
|
||||||
buf.append(line)
|
|
||||||
buf.append(" " * (clear_width - line_len))
|
|
||||||
line = "".join(buf)
|
|
||||||
# Render the line only if it changed.
|
|
||||||
|
|
||||||
if line != self._last_line:
|
|
||||||
self._last_line = line
|
|
||||||
echo(line, file=self.file, color=self.color, nl=False)
|
|
||||||
self.file.flush()
|
|
||||||
|
|
||||||
def make_step(self, n_steps: int) -> None:
|
|
||||||
self.pos += n_steps
|
|
||||||
if self.length is not None and self.pos >= self.length:
|
|
||||||
self.finished = True
|
|
||||||
|
|
||||||
if (time.time() - self.last_eta) < 1.0:
|
|
||||||
return
|
|
||||||
|
|
||||||
self.last_eta = time.time()
|
|
||||||
|
|
||||||
# self.avg is a rolling list of length <= 7 of steps where steps are
|
|
||||||
# defined as time elapsed divided by the total progress through
|
|
||||||
# self.length.
|
|
||||||
if self.pos:
|
|
||||||
step = (time.time() - self.start) / self.pos
|
|
||||||
else:
|
|
||||||
step = time.time() - self.start
|
|
||||||
|
|
||||||
self.avg = self.avg[-6:] + [step]
|
|
||||||
|
|
||||||
self.eta_known = self.length is not None
|
|
||||||
|
|
||||||
def update(self, n_steps: int, current_item: t.Optional[V] = None) -> None:
|
|
||||||
"""Update the progress bar by advancing a specified number of
|
|
||||||
steps, and optionally set the ``current_item`` for this new
|
|
||||||
position.
|
|
||||||
|
|
||||||
:param n_steps: Number of steps to advance.
|
|
||||||
:param current_item: Optional item to set as ``current_item``
|
|
||||||
for the updated position.
|
|
||||||
|
|
||||||
.. versionchanged:: 8.0
|
|
||||||
Added the ``current_item`` optional parameter.
|
|
||||||
|
|
||||||
.. versionchanged:: 8.0
|
|
||||||
Only render when the number of steps meets the
|
|
||||||
``update_min_steps`` threshold.
|
|
||||||
"""
|
|
||||||
if current_item is not None:
|
|
||||||
self.current_item = current_item
|
|
||||||
|
|
||||||
self._completed_intervals += n_steps
|
|
||||||
|
|
||||||
if self._completed_intervals >= self.update_min_steps:
|
|
||||||
self.make_step(self._completed_intervals)
|
|
||||||
self.render_progress()
|
|
||||||
self._completed_intervals = 0
|
|
||||||
|
|
||||||
def finish(self) -> None:
|
|
||||||
self.eta_known = False
|
|
||||||
self.current_item = None
|
|
||||||
self.finished = True
|
|
||||||
|
|
||||||
def generator(self) -> t.Iterator[V]:
|
|
||||||
"""Return a generator which yields the items added to the bar
|
|
||||||
during construction, and updates the progress bar *after* the
|
|
||||||
yielded block returns.
|
|
||||||
"""
|
|
||||||
# WARNING: the iterator interface for `ProgressBar` relies on
|
|
||||||
# this and only works because this is a simple generator which
|
|
||||||
# doesn't create or manage additional state. If this function
|
|
||||||
# changes, the impact should be evaluated both against
|
|
||||||
# `iter(bar)` and `next(bar)`. `next()` in particular may call
|
|
||||||
# `self.generator()` repeatedly, and this must remain safe in
|
|
||||||
# order for that interface to work.
|
|
||||||
if not self.entered:
|
|
||||||
raise RuntimeError("You need to use progress bars in a with block.")
|
|
||||||
|
|
||||||
if self.is_hidden:
|
|
||||||
yield from self.iter
|
|
||||||
else:
|
|
||||||
for rv in self.iter:
|
|
||||||
self.current_item = rv
|
|
||||||
|
|
||||||
# This allows show_item_func to be updated before the
|
|
||||||
# item is processed. Only trigger at the beginning of
|
|
||||||
# the update interval.
|
|
||||||
if self._completed_intervals == 0:
|
|
||||||
self.render_progress()
|
|
||||||
|
|
||||||
yield rv
|
|
||||||
self.update(1)
|
|
||||||
|
|
||||||
self.finish()
|
|
||||||
self.render_progress()
|
|
||||||
|
|
||||||
|
|
||||||
def pager(generator: t.Iterable[str], color: t.Optional[bool] = None) -> None:
|
|
||||||
"""Decide what method to use for paging through text."""
|
|
||||||
stdout = _default_text_stdout()
|
|
||||||
if not isatty(sys.stdin) or not isatty(stdout):
|
|
||||||
return _nullpager(stdout, generator, color)
|
|
||||||
pager_cmd = (os.environ.get("PAGER", None) or "").strip()
|
|
||||||
if pager_cmd:
|
|
||||||
if WIN:
|
|
||||||
return _tempfilepager(generator, pager_cmd, color)
|
|
||||||
return _pipepager(generator, pager_cmd, color)
|
|
||||||
if os.environ.get("TERM") in ("dumb", "emacs"):
|
|
||||||
return _nullpager(stdout, generator, color)
|
|
||||||
if WIN or sys.platform.startswith("os2"):
|
|
||||||
return _tempfilepager(generator, "more <", color)
|
|
||||||
if hasattr(os, "system") and os.system("(less) 2>/dev/null") == 0:
|
|
||||||
return _pipepager(generator, "less", color)
|
|
||||||
|
|
||||||
import tempfile
|
|
||||||
|
|
||||||
fd, filename = tempfile.mkstemp()
|
|
||||||
os.close(fd)
|
|
||||||
try:
|
|
||||||
if hasattr(os, "system") and os.system(f'more "{filename}"') == 0:
|
|
||||||
return _pipepager(generator, "more", color)
|
|
||||||
return _nullpager(stdout, generator, color)
|
|
||||||
finally:
|
|
||||||
os.unlink(filename)
|
|
||||||
|
|
||||||
|
|
||||||
def _pipepager(generator: t.Iterable[str], cmd: str, color: t.Optional[bool]) -> None:
|
|
||||||
"""Page through text by feeding it to another program. Invoking a
|
|
||||||
pager through this might support colors.
|
|
||||||
"""
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
env = dict(os.environ)
|
|
||||||
|
|
||||||
# If we're piping to less we might support colors under the
|
|
||||||
# condition that
|
|
||||||
cmd_detail = cmd.rsplit("/", 1)[-1].split()
|
|
||||||
if color is None and cmd_detail[0] == "less":
|
|
||||||
less_flags = f"{os.environ.get('LESS', '')}{' '.join(cmd_detail[1:])}"
|
|
||||||
if not less_flags:
|
|
||||||
env["LESS"] = "-R"
|
|
||||||
color = True
|
|
||||||
elif "r" in less_flags or "R" in less_flags:
|
|
||||||
color = True
|
|
||||||
|
|
||||||
c = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, env=env)
|
|
||||||
stdin = t.cast(t.BinaryIO, c.stdin)
|
|
||||||
encoding = get_best_encoding(stdin)
|
|
||||||
try:
|
|
||||||
for text in generator:
|
|
||||||
if not color:
|
|
||||||
text = strip_ansi(text)
|
|
||||||
|
|
||||||
stdin.write(text.encode(encoding, "replace"))
|
|
||||||
except (OSError, KeyboardInterrupt):
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
stdin.close()
|
|
||||||
|
|
||||||
# Less doesn't respect ^C, but catches it for its own UI purposes (aborting
|
|
||||||
# search or other commands inside less).
|
|
||||||
#
|
|
||||||
# That means when the user hits ^C, the parent process (click) terminates,
|
|
||||||
# but less is still alive, paging the output and messing up the terminal.
|
|
||||||
#
|
|
||||||
# If the user wants to make the pager exit on ^C, they should set
|
|
||||||
# `LESS='-K'`. It's not our decision to make.
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
c.wait()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
|
|
||||||
|
|
||||||
def _tempfilepager(
|
|
||||||
generator: t.Iterable[str], cmd: str, color: t.Optional[bool]
|
|
||||||
) -> None:
|
|
||||||
"""Page through text by invoking a program on a temporary file."""
|
|
||||||
import tempfile
|
|
||||||
|
|
||||||
_, filename = tempfile.mkstemp()
|
|
||||||
# TODO: This never terminates if the passed generator never terminates.
|
|
||||||
text = "".join(generator)
|
|
||||||
if not color:
|
|
||||||
text = strip_ansi(text)
|
|
||||||
encoding = get_best_encoding(sys.stdout)
|
|
||||||
with open_stream(filename, "wb")[0] as f:
|
|
||||||
f.write(text.encode(encoding))
|
|
||||||
try:
|
|
||||||
os.system(f'{cmd} "{filename}"')
|
|
||||||
finally:
|
|
||||||
os.unlink(filename)
|
|
||||||
|
|
||||||
|
|
||||||
def _nullpager(
|
|
||||||
stream: t.TextIO, generator: t.Iterable[str], color: t.Optional[bool]
|
|
||||||
) -> None:
|
|
||||||
"""Simply print unformatted text. This is the ultimate fallback."""
|
|
||||||
for text in generator:
|
|
||||||
if not color:
|
|
||||||
text = strip_ansi(text)
|
|
||||||
stream.write(text)
|
|
||||||
|
|
||||||
|
|
||||||
class Editor:
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
editor: t.Optional[str] = None,
|
|
||||||
env: t.Optional[t.Mapping[str, str]] = None,
|
|
||||||
require_save: bool = True,
|
|
||||||
extension: str = ".txt",
|
|
||||||
) -> None:
|
|
||||||
self.editor = editor
|
|
||||||
self.env = env
|
|
||||||
self.require_save = require_save
|
|
||||||
self.extension = extension
|
|
||||||
|
|
||||||
def get_editor(self) -> str:
|
|
||||||
if self.editor is not None:
|
|
||||||
return self.editor
|
|
||||||
for key in "VISUAL", "EDITOR":
|
|
||||||
rv = os.environ.get(key)
|
|
||||||
if rv:
|
|
||||||
return rv
|
|
||||||
if WIN:
|
|
||||||
return "notepad"
|
|
||||||
for editor in "sensible-editor", "vim", "nano":
|
|
||||||
if os.system(f"which {editor} >/dev/null 2>&1") == 0:
|
|
||||||
return editor
|
|
||||||
return "vi"
|
|
||||||
|
|
||||||
def edit_file(self, filename: str) -> None:
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
editor = self.get_editor()
|
|
||||||
environ: t.Optional[t.Dict[str, str]] = None
|
|
||||||
|
|
||||||
if self.env:
|
|
||||||
environ = os.environ.copy()
|
|
||||||
environ.update(self.env)
|
|
||||||
|
|
||||||
try:
|
|
||||||
c = subprocess.Popen(f'{editor} "{filename}"', env=environ, shell=True)
|
|
||||||
exit_code = c.wait()
|
|
||||||
if exit_code != 0:
|
|
||||||
raise ClickException(
|
|
||||||
_("{editor}: Editing failed").format(editor=editor)
|
|
||||||
)
|
|
||||||
except OSError as e:
|
|
||||||
raise ClickException(
|
|
||||||
_("{editor}: Editing failed: {e}").format(editor=editor, e=e)
|
|
||||||
)
|
|
||||||
|
|
||||||
def edit(self, text: t.Optional[t.AnyStr]) -> t.Optional[t.AnyStr]:
|
|
||||||
import tempfile
|
|
||||||
|
|
||||||
if not text:
|
|
||||||
data = b""
|
|
||||||
elif isinstance(text, (bytes, bytearray)):
|
|
||||||
data = text
|
|
||||||
else:
|
|
||||||
if text and not text.endswith("\n"):
|
|
||||||
text += "\n"
|
|
||||||
|
|
||||||
if WIN:
|
|
||||||
data = text.replace("\n", "\r\n").encode("utf-8-sig")
|
|
||||||
else:
|
|
||||||
data = text.encode("utf-8")
|
|
||||||
|
|
||||||
fd, name = tempfile.mkstemp(prefix="editor-", suffix=self.extension)
|
|
||||||
f: t.BinaryIO
|
|
||||||
|
|
||||||
try:
|
|
||||||
with os.fdopen(fd, "wb") as f:
|
|
||||||
f.write(data)
|
|
||||||
|
|
||||||
# If the filesystem resolution is 1 second, like Mac OS
|
|
||||||
# 10.12 Extended, or 2 seconds, like FAT32, and the editor
|
|
||||||
# closes very fast, require_save can fail. Set the modified
|
|
||||||
# time to be 2 seconds in the past to work around this.
|
|
||||||
os.utime(name, (os.path.getatime(name), os.path.getmtime(name) - 2))
|
|
||||||
# Depending on the resolution, the exact value might not be
|
|
||||||
# recorded, so get the new recorded value.
|
|
||||||
timestamp = os.path.getmtime(name)
|
|
||||||
|
|
||||||
self.edit_file(name)
|
|
||||||
|
|
||||||
if self.require_save and os.path.getmtime(name) == timestamp:
|
|
||||||
return None
|
|
||||||
|
|
||||||
with open(name, "rb") as f:
|
|
||||||
rv = f.read()
|
|
||||||
|
|
||||||
if isinstance(text, (bytes, bytearray)):
|
|
||||||
return rv
|
|
||||||
|
|
||||||
return rv.decode("utf-8-sig").replace("\r\n", "\n") # type: ignore
|
|
||||||
finally:
|
|
||||||
os.unlink(name)
|
|
||||||
|
|
||||||
|
|
||||||
def open_url(url: str, wait: bool = False, locate: bool = False) -> int:
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
def _unquote_file(url: str) -> str:
|
|
||||||
from urllib.parse import unquote
|
|
||||||
|
|
||||||
if url.startswith("file://"):
|
|
||||||
url = unquote(url[7:])
|
|
||||||
|
|
||||||
return url
|
|
||||||
|
|
||||||
if sys.platform == "darwin":
|
|
||||||
args = ["open"]
|
|
||||||
if wait:
|
|
||||||
args.append("-W")
|
|
||||||
if locate:
|
|
||||||
args.append("-R")
|
|
||||||
args.append(_unquote_file(url))
|
|
||||||
null = open("/dev/null", "w")
|
|
||||||
try:
|
|
||||||
return subprocess.Popen(args, stderr=null).wait()
|
|
||||||
finally:
|
|
||||||
null.close()
|
|
||||||
elif WIN:
|
|
||||||
if locate:
|
|
||||||
url = _unquote_file(url.replace('"', ""))
|
|
||||||
args = f'explorer /select,"{url}"'
|
|
||||||
else:
|
|
||||||
url = url.replace('"', "")
|
|
||||||
wait_str = "/WAIT" if wait else ""
|
|
||||||
args = f'start {wait_str} "" "{url}"'
|
|
||||||
return os.system(args)
|
|
||||||
elif CYGWIN:
|
|
||||||
if locate:
|
|
||||||
url = os.path.dirname(_unquote_file(url).replace('"', ""))
|
|
||||||
args = f'cygstart "{url}"'
|
|
||||||
else:
|
|
||||||
url = url.replace('"', "")
|
|
||||||
wait_str = "-w" if wait else ""
|
|
||||||
args = f'cygstart {wait_str} "{url}"'
|
|
||||||
return os.system(args)
|
|
||||||
|
|
||||||
try:
|
|
||||||
if locate:
|
|
||||||
url = os.path.dirname(_unquote_file(url)) or "."
|
|
||||||
else:
|
|
||||||
url = _unquote_file(url)
|
|
||||||
c = subprocess.Popen(["xdg-open", url])
|
|
||||||
if wait:
|
|
||||||
return c.wait()
|
|
||||||
return 0
|
|
||||||
except OSError:
|
|
||||||
if url.startswith(("http://", "https://")) and not locate and not wait:
|
|
||||||
import webbrowser
|
|
||||||
|
|
||||||
webbrowser.open(url)
|
|
||||||
return 0
|
|
||||||
return 1
|
|
||||||
|
|
||||||
|
|
||||||
def _translate_ch_to_exc(ch: str) -> t.Optional[BaseException]:
|
|
||||||
if ch == "\x03":
|
|
||||||
raise KeyboardInterrupt()
|
|
||||||
|
|
||||||
if ch == "\x04" and not WIN: # Unix-like, Ctrl+D
|
|
||||||
raise EOFError()
|
|
||||||
|
|
||||||
if ch == "\x1a" and WIN: # Windows, Ctrl+Z
|
|
||||||
raise EOFError()
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
if WIN:
|
|
||||||
import msvcrt
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def raw_terminal() -> t.Iterator[int]:
|
|
||||||
yield -1
|
|
||||||
|
|
||||||
def getchar(echo: bool) -> str:
|
|
||||||
# The function `getch` will return a bytes object corresponding to
|
|
||||||
# the pressed character. Since Windows 10 build 1803, it will also
|
|
||||||
# return \x00 when called a second time after pressing a regular key.
|
|
||||||
#
|
|
||||||
# `getwch` does not share this probably-bugged behavior. Moreover, it
|
|
||||||
# returns a Unicode object by default, which is what we want.
|
|
||||||
#
|
|
||||||
# Either of these functions will return \x00 or \xe0 to indicate
|
|
||||||
# a special key, and you need to call the same function again to get
|
|
||||||
# the "rest" of the code. The fun part is that \u00e0 is
|
|
||||||
# "latin small letter a with grave", so if you type that on a French
|
|
||||||
# keyboard, you _also_ get a \xe0.
|
|
||||||
# E.g., consider the Up arrow. This returns \xe0 and then \x48. The
|
|
||||||
# resulting Unicode string reads as "a with grave" + "capital H".
|
|
||||||
# This is indistinguishable from when the user actually types
|
|
||||||
# "a with grave" and then "capital H".
|
|
||||||
#
|
|
||||||
# When \xe0 is returned, we assume it's part of a special-key sequence
|
|
||||||
# and call `getwch` again, but that means that when the user types
|
|
||||||
# the \u00e0 character, `getchar` doesn't return until a second
|
|
||||||
# character is typed.
|
|
||||||
# The alternative is returning immediately, but that would mess up
|
|
||||||
# cross-platform handling of arrow keys and others that start with
|
|
||||||
# \xe0. Another option is using `getch`, but then we can't reliably
|
|
||||||
# read non-ASCII characters, because return values of `getch` are
|
|
||||||
# limited to the current 8-bit codepage.
|
|
||||||
#
|
|
||||||
# Anyway, Click doesn't claim to do this Right(tm), and using `getwch`
|
|
||||||
# is doing the right thing in more situations than with `getch`.
|
|
||||||
func: t.Callable[[], str]
|
|
||||||
|
|
||||||
if echo:
|
|
||||||
func = msvcrt.getwche # type: ignore
|
|
||||||
else:
|
|
||||||
func = msvcrt.getwch # type: ignore
|
|
||||||
|
|
||||||
rv = func()
|
|
||||||
|
|
||||||
if rv in ("\x00", "\xe0"):
|
|
||||||
# \x00 and \xe0 are control characters that indicate special key,
|
|
||||||
# see above.
|
|
||||||
rv += func()
|
|
||||||
|
|
||||||
_translate_ch_to_exc(rv)
|
|
||||||
return rv
|
|
||||||
|
|
||||||
|
|
||||||
else:
|
|
||||||
import tty
|
|
||||||
import termios
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def raw_terminal() -> t.Iterator[int]:
|
|
||||||
f: t.Optional[t.TextIO]
|
|
||||||
fd: int
|
|
||||||
|
|
||||||
if not isatty(sys.stdin):
|
|
||||||
f = open("/dev/tty")
|
|
||||||
fd = f.fileno()
|
|
||||||
else:
|
|
||||||
fd = sys.stdin.fileno()
|
|
||||||
f = None
|
|
||||||
|
|
||||||
try:
|
|
||||||
old_settings = termios.tcgetattr(fd)
|
|
||||||
|
|
||||||
try:
|
|
||||||
tty.setraw(fd)
|
|
||||||
yield fd
|
|
||||||
finally:
|
|
||||||
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
|
|
||||||
sys.stdout.flush()
|
|
||||||
|
|
||||||
if f is not None:
|
|
||||||
f.close()
|
|
||||||
except termios.error:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def getchar(echo: bool) -> str:
|
|
||||||
with raw_terminal() as fd:
|
|
||||||
ch = os.read(fd, 32).decode(get_best_encoding(sys.stdin), "replace")
|
|
||||||
|
|
||||||
if echo and isatty(sys.stdout):
|
|
||||||
sys.stdout.write(ch)
|
|
||||||
|
|
||||||
_translate_ch_to_exc(ch)
|
|
||||||
return ch
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
import textwrap
|
|
||||||
import typing as t
|
|
||||||
from contextlib import contextmanager
|
|
||||||
|
|
||||||
|
|
||||||
class TextWrapper(textwrap.TextWrapper):
|
|
||||||
def _handle_long_word(
|
|
||||||
self,
|
|
||||||
reversed_chunks: t.List[str],
|
|
||||||
cur_line: t.List[str],
|
|
||||||
cur_len: int,
|
|
||||||
width: int,
|
|
||||||
) -> None:
|
|
||||||
space_left = max(width - cur_len, 1)
|
|
||||||
|
|
||||||
if self.break_long_words:
|
|
||||||
last = reversed_chunks[-1]
|
|
||||||
cut = last[:space_left]
|
|
||||||
res = last[space_left:]
|
|
||||||
cur_line.append(cut)
|
|
||||||
reversed_chunks[-1] = res
|
|
||||||
elif not cur_line:
|
|
||||||
cur_line.append(reversed_chunks.pop())
|
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def extra_indent(self, indent: str) -> t.Iterator[None]:
|
|
||||||
old_initial_indent = self.initial_indent
|
|
||||||
old_subsequent_indent = self.subsequent_indent
|
|
||||||
self.initial_indent += indent
|
|
||||||
self.subsequent_indent += indent
|
|
||||||
|
|
||||||
try:
|
|
||||||
yield
|
|
||||||
finally:
|
|
||||||
self.initial_indent = old_initial_indent
|
|
||||||
self.subsequent_indent = old_subsequent_indent
|
|
||||||
|
|
||||||
def indent_only(self, text: str) -> str:
|
|
||||||
rv = []
|
|
||||||
|
|
||||||
for idx, line in enumerate(text.splitlines()):
|
|
||||||
indent = self.initial_indent
|
|
||||||
|
|
||||||
if idx > 0:
|
|
||||||
indent = self.subsequent_indent
|
|
||||||
|
|
||||||
rv.append(f"{indent}{line}")
|
|
||||||
|
|
||||||
return "\n".join(rv)
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
import codecs
|
|
||||||
import os
|
|
||||||
from gettext import gettext as _
|
|
||||||
|
|
||||||
|
|
||||||
def _verify_python_env() -> None:
|
|
||||||
"""Ensures that the environment is good for Unicode."""
|
|
||||||
try:
|
|
||||||
from locale import getpreferredencoding
|
|
||||||
|
|
||||||
fs_enc = codecs.lookup(getpreferredencoding()).name
|
|
||||||
except Exception:
|
|
||||||
fs_enc = "ascii"
|
|
||||||
|
|
||||||
if fs_enc != "ascii":
|
|
||||||
return
|
|
||||||
|
|
||||||
extra = [
|
|
||||||
_(
|
|
||||||
"Click will abort further execution because Python was"
|
|
||||||
" configured to use ASCII as encoding for the environment."
|
|
||||||
" Consult https://click.palletsprojects.com/unicode-support/"
|
|
||||||
" for mitigation steps."
|
|
||||||
)
|
|
||||||
]
|
|
||||||
|
|
||||||
if os.name == "posix":
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
try:
|
|
||||||
rv = subprocess.Popen(
|
|
||||||
["locale", "-a"],
|
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
stderr=subprocess.PIPE,
|
|
||||||
encoding="ascii",
|
|
||||||
errors="replace",
|
|
||||||
).communicate()[0]
|
|
||||||
except OSError:
|
|
||||||
rv = ""
|
|
||||||
|
|
||||||
good_locales = set()
|
|
||||||
has_c_utf8 = False
|
|
||||||
|
|
||||||
for line in rv.splitlines():
|
|
||||||
locale = line.strip()
|
|
||||||
|
|
||||||
if locale.lower().endswith((".utf-8", ".utf8")):
|
|
||||||
good_locales.add(locale)
|
|
||||||
|
|
||||||
if locale.lower() in ("c.utf8", "c.utf-8"):
|
|
||||||
has_c_utf8 = True
|
|
||||||
|
|
||||||
if not good_locales:
|
|
||||||
extra.append(
|
|
||||||
_(
|
|
||||||
"Additional information: on this system no suitable"
|
|
||||||
" UTF-8 locales were discovered. This most likely"
|
|
||||||
" requires resolving by reconfiguring the locale"
|
|
||||||
" system."
|
|
||||||
)
|
|
||||||
)
|
|
||||||
elif has_c_utf8:
|
|
||||||
extra.append(
|
|
||||||
_(
|
|
||||||
"This system supports the C.UTF-8 locale which is"
|
|
||||||
" recommended. You might be able to resolve your"
|
|
||||||
" issue by exporting the following environment"
|
|
||||||
" variables:"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
extra.append(" export LC_ALL=C.UTF-8\n export LANG=C.UTF-8")
|
|
||||||
else:
|
|
||||||
extra.append(
|
|
||||||
_(
|
|
||||||
"This system lists some UTF-8 supporting locales"
|
|
||||||
" that you can pick from. The following suitable"
|
|
||||||
" locales were discovered: {locales}"
|
|
||||||
).format(locales=", ".join(sorted(good_locales)))
|
|
||||||
)
|
|
||||||
|
|
||||||
bad_locale = None
|
|
||||||
|
|
||||||
for env_locale in os.environ.get("LC_ALL"), os.environ.get("LANG"):
|
|
||||||
if env_locale and env_locale.lower().endswith((".utf-8", ".utf8")):
|
|
||||||
bad_locale = env_locale
|
|
||||||
|
|
||||||
if env_locale is not None:
|
|
||||||
break
|
|
||||||
|
|
||||||
if bad_locale is not None:
|
|
||||||
extra.append(
|
|
||||||
_(
|
|
||||||
"Click discovered that you exported a UTF-8 locale"
|
|
||||||
" but the locale system could not pick up from it"
|
|
||||||
" because it does not exist. The exported locale is"
|
|
||||||
" {locale!r} but it is not supported."
|
|
||||||
).format(locale=bad_locale)
|
|
||||||
)
|
|
||||||
|
|
||||||
raise RuntimeError("\n\n".join(extra))
|
|
||||||
@@ -1,279 +0,0 @@
|
|||||||
# This module is based on the excellent work by Adam Bartoš who
|
|
||||||
# provided a lot of what went into the implementation here in
|
|
||||||
# the discussion to issue1602 in the Python bug tracker.
|
|
||||||
#
|
|
||||||
# There are some general differences in regards to how this works
|
|
||||||
# compared to the original patches as we do not need to patch
|
|
||||||
# the entire interpreter but just work in our little world of
|
|
||||||
# echo and prompt.
|
|
||||||
import io
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
import typing as t
|
|
||||||
from ctypes import byref
|
|
||||||
from ctypes import c_char
|
|
||||||
from ctypes import c_char_p
|
|
||||||
from ctypes import c_int
|
|
||||||
from ctypes import c_ssize_t
|
|
||||||
from ctypes import c_ulong
|
|
||||||
from ctypes import c_void_p
|
|
||||||
from ctypes import POINTER
|
|
||||||
from ctypes import py_object
|
|
||||||
from ctypes import Structure
|
|
||||||
from ctypes.wintypes import DWORD
|
|
||||||
from ctypes.wintypes import HANDLE
|
|
||||||
from ctypes.wintypes import LPCWSTR
|
|
||||||
from ctypes.wintypes import LPWSTR
|
|
||||||
|
|
||||||
from ._compat import _NonClosingTextIOWrapper
|
|
||||||
|
|
||||||
assert sys.platform == "win32"
|
|
||||||
import msvcrt # noqa: E402
|
|
||||||
from ctypes import windll # noqa: E402
|
|
||||||
from ctypes import WINFUNCTYPE # noqa: E402
|
|
||||||
|
|
||||||
c_ssize_p = POINTER(c_ssize_t)
|
|
||||||
|
|
||||||
kernel32 = windll.kernel32
|
|
||||||
GetStdHandle = kernel32.GetStdHandle
|
|
||||||
ReadConsoleW = kernel32.ReadConsoleW
|
|
||||||
WriteConsoleW = kernel32.WriteConsoleW
|
|
||||||
GetConsoleMode = kernel32.GetConsoleMode
|
|
||||||
GetLastError = kernel32.GetLastError
|
|
||||||
GetCommandLineW = WINFUNCTYPE(LPWSTR)(("GetCommandLineW", windll.kernel32))
|
|
||||||
CommandLineToArgvW = WINFUNCTYPE(POINTER(LPWSTR), LPCWSTR, POINTER(c_int))(
|
|
||||||
("CommandLineToArgvW", windll.shell32)
|
|
||||||
)
|
|
||||||
LocalFree = WINFUNCTYPE(c_void_p, c_void_p)(("LocalFree", windll.kernel32))
|
|
||||||
|
|
||||||
STDIN_HANDLE = GetStdHandle(-10)
|
|
||||||
STDOUT_HANDLE = GetStdHandle(-11)
|
|
||||||
STDERR_HANDLE = GetStdHandle(-12)
|
|
||||||
|
|
||||||
PyBUF_SIMPLE = 0
|
|
||||||
PyBUF_WRITABLE = 1
|
|
||||||
|
|
||||||
ERROR_SUCCESS = 0
|
|
||||||
ERROR_NOT_ENOUGH_MEMORY = 8
|
|
||||||
ERROR_OPERATION_ABORTED = 995
|
|
||||||
|
|
||||||
STDIN_FILENO = 0
|
|
||||||
STDOUT_FILENO = 1
|
|
||||||
STDERR_FILENO = 2
|
|
||||||
|
|
||||||
EOF = b"\x1a"
|
|
||||||
MAX_BYTES_WRITTEN = 32767
|
|
||||||
|
|
||||||
try:
|
|
||||||
from ctypes import pythonapi
|
|
||||||
except ImportError:
|
|
||||||
# On PyPy we cannot get buffers so our ability to operate here is
|
|
||||||
# severely limited.
|
|
||||||
get_buffer = None
|
|
||||||
else:
|
|
||||||
|
|
||||||
class Py_buffer(Structure):
|
|
||||||
_fields_ = [
|
|
||||||
("buf", c_void_p),
|
|
||||||
("obj", py_object),
|
|
||||||
("len", c_ssize_t),
|
|
||||||
("itemsize", c_ssize_t),
|
|
||||||
("readonly", c_int),
|
|
||||||
("ndim", c_int),
|
|
||||||
("format", c_char_p),
|
|
||||||
("shape", c_ssize_p),
|
|
||||||
("strides", c_ssize_p),
|
|
||||||
("suboffsets", c_ssize_p),
|
|
||||||
("internal", c_void_p),
|
|
||||||
]
|
|
||||||
|
|
||||||
PyObject_GetBuffer = pythonapi.PyObject_GetBuffer
|
|
||||||
PyBuffer_Release = pythonapi.PyBuffer_Release
|
|
||||||
|
|
||||||
def get_buffer(obj, writable=False):
|
|
||||||
buf = Py_buffer()
|
|
||||||
flags = PyBUF_WRITABLE if writable else PyBUF_SIMPLE
|
|
||||||
PyObject_GetBuffer(py_object(obj), byref(buf), flags)
|
|
||||||
|
|
||||||
try:
|
|
||||||
buffer_type = c_char * buf.len
|
|
||||||
return buffer_type.from_address(buf.buf)
|
|
||||||
finally:
|
|
||||||
PyBuffer_Release(byref(buf))
|
|
||||||
|
|
||||||
|
|
||||||
class _WindowsConsoleRawIOBase(io.RawIOBase):
|
|
||||||
def __init__(self, handle):
|
|
||||||
self.handle = handle
|
|
||||||
|
|
||||||
def isatty(self):
|
|
||||||
super().isatty()
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class _WindowsConsoleReader(_WindowsConsoleRawIOBase):
|
|
||||||
def readable(self):
|
|
||||||
return True
|
|
||||||
|
|
||||||
def readinto(self, b):
|
|
||||||
bytes_to_be_read = len(b)
|
|
||||||
if not bytes_to_be_read:
|
|
||||||
return 0
|
|
||||||
elif bytes_to_be_read % 2:
|
|
||||||
raise ValueError(
|
|
||||||
"cannot read odd number of bytes from UTF-16-LE encoded console"
|
|
||||||
)
|
|
||||||
|
|
||||||
buffer = get_buffer(b, writable=True)
|
|
||||||
code_units_to_be_read = bytes_to_be_read // 2
|
|
||||||
code_units_read = c_ulong()
|
|
||||||
|
|
||||||
rv = ReadConsoleW(
|
|
||||||
HANDLE(self.handle),
|
|
||||||
buffer,
|
|
||||||
code_units_to_be_read,
|
|
||||||
byref(code_units_read),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
if GetLastError() == ERROR_OPERATION_ABORTED:
|
|
||||||
# wait for KeyboardInterrupt
|
|
||||||
time.sleep(0.1)
|
|
||||||
if not rv:
|
|
||||||
raise OSError(f"Windows error: {GetLastError()}")
|
|
||||||
|
|
||||||
if buffer[0] == EOF:
|
|
||||||
return 0
|
|
||||||
return 2 * code_units_read.value
|
|
||||||
|
|
||||||
|
|
||||||
class _WindowsConsoleWriter(_WindowsConsoleRawIOBase):
|
|
||||||
def writable(self):
|
|
||||||
return True
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _get_error_message(errno):
|
|
||||||
if errno == ERROR_SUCCESS:
|
|
||||||
return "ERROR_SUCCESS"
|
|
||||||
elif errno == ERROR_NOT_ENOUGH_MEMORY:
|
|
||||||
return "ERROR_NOT_ENOUGH_MEMORY"
|
|
||||||
return f"Windows error {errno}"
|
|
||||||
|
|
||||||
def write(self, b):
|
|
||||||
bytes_to_be_written = len(b)
|
|
||||||
buf = get_buffer(b)
|
|
||||||
code_units_to_be_written = min(bytes_to_be_written, MAX_BYTES_WRITTEN) // 2
|
|
||||||
code_units_written = c_ulong()
|
|
||||||
|
|
||||||
WriteConsoleW(
|
|
||||||
HANDLE(self.handle),
|
|
||||||
buf,
|
|
||||||
code_units_to_be_written,
|
|
||||||
byref(code_units_written),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
bytes_written = 2 * code_units_written.value
|
|
||||||
|
|
||||||
if bytes_written == 0 and bytes_to_be_written > 0:
|
|
||||||
raise OSError(self._get_error_message(GetLastError()))
|
|
||||||
return bytes_written
|
|
||||||
|
|
||||||
|
|
||||||
class ConsoleStream:
|
|
||||||
def __init__(self, text_stream: t.TextIO, byte_stream: t.BinaryIO) -> None:
|
|
||||||
self._text_stream = text_stream
|
|
||||||
self.buffer = byte_stream
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self) -> str:
|
|
||||||
return self.buffer.name
|
|
||||||
|
|
||||||
def write(self, x: t.AnyStr) -> int:
|
|
||||||
if isinstance(x, str):
|
|
||||||
return self._text_stream.write(x)
|
|
||||||
try:
|
|
||||||
self.flush()
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
return self.buffer.write(x)
|
|
||||||
|
|
||||||
def writelines(self, lines: t.Iterable[t.AnyStr]) -> None:
|
|
||||||
for line in lines:
|
|
||||||
self.write(line)
|
|
||||||
|
|
||||||
def __getattr__(self, name: str) -> t.Any:
|
|
||||||
return getattr(self._text_stream, name)
|
|
||||||
|
|
||||||
def isatty(self) -> bool:
|
|
||||||
return self.buffer.isatty()
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f"<ConsoleStream name={self.name!r} encoding={self.encoding!r}>"
|
|
||||||
|
|
||||||
|
|
||||||
def _get_text_stdin(buffer_stream: t.BinaryIO) -> t.TextIO:
|
|
||||||
text_stream = _NonClosingTextIOWrapper(
|
|
||||||
io.BufferedReader(_WindowsConsoleReader(STDIN_HANDLE)),
|
|
||||||
"utf-16-le",
|
|
||||||
"strict",
|
|
||||||
line_buffering=True,
|
|
||||||
)
|
|
||||||
return t.cast(t.TextIO, ConsoleStream(text_stream, buffer_stream))
|
|
||||||
|
|
||||||
|
|
||||||
def _get_text_stdout(buffer_stream: t.BinaryIO) -> t.TextIO:
|
|
||||||
text_stream = _NonClosingTextIOWrapper(
|
|
||||||
io.BufferedWriter(_WindowsConsoleWriter(STDOUT_HANDLE)),
|
|
||||||
"utf-16-le",
|
|
||||||
"strict",
|
|
||||||
line_buffering=True,
|
|
||||||
)
|
|
||||||
return t.cast(t.TextIO, ConsoleStream(text_stream, buffer_stream))
|
|
||||||
|
|
||||||
|
|
||||||
def _get_text_stderr(buffer_stream: t.BinaryIO) -> t.TextIO:
|
|
||||||
text_stream = _NonClosingTextIOWrapper(
|
|
||||||
io.BufferedWriter(_WindowsConsoleWriter(STDERR_HANDLE)),
|
|
||||||
"utf-16-le",
|
|
||||||
"strict",
|
|
||||||
line_buffering=True,
|
|
||||||
)
|
|
||||||
return t.cast(t.TextIO, ConsoleStream(text_stream, buffer_stream))
|
|
||||||
|
|
||||||
|
|
||||||
_stream_factories: t.Mapping[int, t.Callable[[t.BinaryIO], t.TextIO]] = {
|
|
||||||
0: _get_text_stdin,
|
|
||||||
1: _get_text_stdout,
|
|
||||||
2: _get_text_stderr,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def _is_console(f: t.TextIO) -> bool:
|
|
||||||
if not hasattr(f, "fileno"):
|
|
||||||
return False
|
|
||||||
|
|
||||||
try:
|
|
||||||
fileno = f.fileno()
|
|
||||||
except (OSError, io.UnsupportedOperation):
|
|
||||||
return False
|
|
||||||
|
|
||||||
handle = msvcrt.get_osfhandle(fileno)
|
|
||||||
return bool(GetConsoleMode(handle, byref(DWORD())))
|
|
||||||
|
|
||||||
|
|
||||||
def _get_windows_console_stream(
|
|
||||||
f: t.TextIO, encoding: t.Optional[str], errors: t.Optional[str]
|
|
||||||
) -> t.Optional[t.TextIO]:
|
|
||||||
if (
|
|
||||||
get_buffer is not None
|
|
||||||
and encoding in {"utf-16-le", None}
|
|
||||||
and errors in {"strict", None}
|
|
||||||
and _is_console(f)
|
|
||||||
):
|
|
||||||
func = _stream_factories.get(f.fileno())
|
|
||||||
if func is not None:
|
|
||||||
b = getattr(f, "buffer", None)
|
|
||||||
|
|
||||||
if b is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return func(b)
|
|
||||||
@@ -1,437 +0,0 @@
|
|||||||
import inspect
|
|
||||||
import types
|
|
||||||
import typing as t
|
|
||||||
from functools import update_wrapper
|
|
||||||
from gettext import gettext as _
|
|
||||||
|
|
||||||
from .core import Argument
|
|
||||||
from .core import Command
|
|
||||||
from .core import Context
|
|
||||||
from .core import Group
|
|
||||||
from .core import Option
|
|
||||||
from .core import Parameter
|
|
||||||
from .globals import get_current_context
|
|
||||||
from .utils import echo
|
|
||||||
|
|
||||||
F = t.TypeVar("F", bound=t.Callable[..., t.Any])
|
|
||||||
FC = t.TypeVar("FC", t.Callable[..., t.Any], Command)
|
|
||||||
|
|
||||||
|
|
||||||
def pass_context(f: F) -> F:
|
|
||||||
"""Marks a callback as wanting to receive the current context
|
|
||||||
object as first argument.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def new_func(*args, **kwargs): # type: ignore
|
|
||||||
return f(get_current_context(), *args, **kwargs)
|
|
||||||
|
|
||||||
return update_wrapper(t.cast(F, new_func), f)
|
|
||||||
|
|
||||||
|
|
||||||
def pass_obj(f: F) -> F:
|
|
||||||
"""Similar to :func:`pass_context`, but only pass the object on the
|
|
||||||
context onwards (:attr:`Context.obj`). This is useful if that object
|
|
||||||
represents the state of a nested system.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def new_func(*args, **kwargs): # type: ignore
|
|
||||||
return f(get_current_context().obj, *args, **kwargs)
|
|
||||||
|
|
||||||
return update_wrapper(t.cast(F, new_func), f)
|
|
||||||
|
|
||||||
|
|
||||||
def make_pass_decorator(
|
|
||||||
object_type: t.Type, ensure: bool = False
|
|
||||||
) -> "t.Callable[[F], F]":
|
|
||||||
"""Given an object type this creates a decorator that will work
|
|
||||||
similar to :func:`pass_obj` but instead of passing the object of the
|
|
||||||
current context, it will find the innermost context of type
|
|
||||||
:func:`object_type`.
|
|
||||||
|
|
||||||
This generates a decorator that works roughly like this::
|
|
||||||
|
|
||||||
from functools import update_wrapper
|
|
||||||
|
|
||||||
def decorator(f):
|
|
||||||
@pass_context
|
|
||||||
def new_func(ctx, *args, **kwargs):
|
|
||||||
obj = ctx.find_object(object_type)
|
|
||||||
return ctx.invoke(f, obj, *args, **kwargs)
|
|
||||||
return update_wrapper(new_func, f)
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
:param object_type: the type of the object to pass.
|
|
||||||
:param ensure: if set to `True`, a new object will be created and
|
|
||||||
remembered on the context if it's not there yet.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def decorator(f: F) -> F:
|
|
||||||
def new_func(*args, **kwargs): # type: ignore
|
|
||||||
ctx = get_current_context()
|
|
||||||
|
|
||||||
if ensure:
|
|
||||||
obj = ctx.ensure_object(object_type)
|
|
||||||
else:
|
|
||||||
obj = ctx.find_object(object_type)
|
|
||||||
|
|
||||||
if obj is None:
|
|
||||||
raise RuntimeError(
|
|
||||||
"Managed to invoke callback without a context"
|
|
||||||
f" object of type {object_type.__name__!r}"
|
|
||||||
" existing."
|
|
||||||
)
|
|
||||||
|
|
||||||
return ctx.invoke(f, obj, *args, **kwargs)
|
|
||||||
|
|
||||||
return update_wrapper(t.cast(F, new_func), f)
|
|
||||||
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
|
|
||||||
def pass_meta_key(
|
|
||||||
key: str, *, doc_description: t.Optional[str] = None
|
|
||||||
) -> "t.Callable[[F], F]":
|
|
||||||
"""Create a decorator that passes a key from
|
|
||||||
:attr:`click.Context.meta` as the first argument to the decorated
|
|
||||||
function.
|
|
||||||
|
|
||||||
:param key: Key in ``Context.meta`` to pass.
|
|
||||||
:param doc_description: Description of the object being passed,
|
|
||||||
inserted into the decorator's docstring. Defaults to "the 'key'
|
|
||||||
key from Context.meta".
|
|
||||||
|
|
||||||
.. versionadded:: 8.0
|
|
||||||
"""
|
|
||||||
|
|
||||||
def decorator(f: F) -> F:
|
|
||||||
def new_func(*args, **kwargs): # type: ignore
|
|
||||||
ctx = get_current_context()
|
|
||||||
obj = ctx.meta[key]
|
|
||||||
return ctx.invoke(f, obj, *args, **kwargs)
|
|
||||||
|
|
||||||
return update_wrapper(t.cast(F, new_func), f)
|
|
||||||
|
|
||||||
if doc_description is None:
|
|
||||||
doc_description = f"the {key!r} key from :attr:`click.Context.meta`"
|
|
||||||
|
|
||||||
decorator.__doc__ = (
|
|
||||||
f"Decorator that passes {doc_description} as the first argument"
|
|
||||||
" to the decorated function."
|
|
||||||
)
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
|
|
||||||
def _make_command(
|
|
||||||
f: F,
|
|
||||||
name: t.Optional[str],
|
|
||||||
attrs: t.MutableMapping[str, t.Any],
|
|
||||||
cls: t.Type[Command],
|
|
||||||
) -> Command:
|
|
||||||
if isinstance(f, Command):
|
|
||||||
raise TypeError("Attempted to convert a callback into a command twice.")
|
|
||||||
|
|
||||||
try:
|
|
||||||
params = f.__click_params__ # type: ignore
|
|
||||||
params.reverse()
|
|
||||||
del f.__click_params__ # type: ignore
|
|
||||||
except AttributeError:
|
|
||||||
params = []
|
|
||||||
|
|
||||||
help = attrs.get("help")
|
|
||||||
|
|
||||||
if help is None:
|
|
||||||
help = inspect.getdoc(f)
|
|
||||||
else:
|
|
||||||
help = inspect.cleandoc(help)
|
|
||||||
|
|
||||||
attrs["help"] = help
|
|
||||||
return cls(
|
|
||||||
name=name or f.__name__.lower().replace("_", "-"),
|
|
||||||
callback=f,
|
|
||||||
params=params,
|
|
||||||
**attrs,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def command(
|
|
||||||
name: t.Optional[str] = None,
|
|
||||||
cls: t.Optional[t.Type[Command]] = None,
|
|
||||||
**attrs: t.Any,
|
|
||||||
) -> t.Callable[[F], Command]:
|
|
||||||
r"""Creates a new :class:`Command` and uses the decorated function as
|
|
||||||
callback. This will also automatically attach all decorated
|
|
||||||
:func:`option`\s and :func:`argument`\s as parameters to the command.
|
|
||||||
|
|
||||||
The name of the command defaults to the name of the function with
|
|
||||||
underscores replaced by dashes. If you want to change that, you can
|
|
||||||
pass the intended name as the first argument.
|
|
||||||
|
|
||||||
All keyword arguments are forwarded to the underlying command class.
|
|
||||||
|
|
||||||
Once decorated the function turns into a :class:`Command` instance
|
|
||||||
that can be invoked as a command line utility or be attached to a
|
|
||||||
command :class:`Group`.
|
|
||||||
|
|
||||||
:param name: the name of the command. This defaults to the function
|
|
||||||
name with underscores replaced by dashes.
|
|
||||||
:param cls: the command class to instantiate. This defaults to
|
|
||||||
:class:`Command`.
|
|
||||||
"""
|
|
||||||
if cls is None:
|
|
||||||
cls = Command
|
|
||||||
|
|
||||||
def decorator(f: t.Callable[..., t.Any]) -> Command:
|
|
||||||
cmd = _make_command(f, name, attrs, cls) # type: ignore
|
|
||||||
cmd.__doc__ = f.__doc__
|
|
||||||
return cmd
|
|
||||||
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
|
|
||||||
def group(name: t.Optional[str] = None, **attrs: t.Any) -> t.Callable[[F], Group]:
|
|
||||||
"""Creates a new :class:`Group` with a function as callback. This
|
|
||||||
works otherwise the same as :func:`command` just that the `cls`
|
|
||||||
parameter is set to :class:`Group`.
|
|
||||||
"""
|
|
||||||
attrs.setdefault("cls", Group)
|
|
||||||
return t.cast(Group, command(name, **attrs))
|
|
||||||
|
|
||||||
|
|
||||||
def _param_memo(f: FC, param: Parameter) -> None:
|
|
||||||
if isinstance(f, Command):
|
|
||||||
f.params.append(param)
|
|
||||||
else:
|
|
||||||
if not hasattr(f, "__click_params__"):
|
|
||||||
f.__click_params__ = [] # type: ignore
|
|
||||||
|
|
||||||
f.__click_params__.append(param) # type: ignore
|
|
||||||
|
|
||||||
|
|
||||||
def argument(*param_decls: str, **attrs: t.Any) -> t.Callable[[FC], FC]:
|
|
||||||
"""Attaches an argument to the command. All positional arguments are
|
|
||||||
passed as parameter declarations to :class:`Argument`; all keyword
|
|
||||||
arguments are forwarded unchanged (except ``cls``).
|
|
||||||
This is equivalent to creating an :class:`Argument` instance manually
|
|
||||||
and attaching it to the :attr:`Command.params` list.
|
|
||||||
|
|
||||||
:param cls: the argument class to instantiate. This defaults to
|
|
||||||
:class:`Argument`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def decorator(f: FC) -> FC:
|
|
||||||
ArgumentClass = attrs.pop("cls", Argument)
|
|
||||||
_param_memo(f, ArgumentClass(param_decls, **attrs))
|
|
||||||
return f
|
|
||||||
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
|
|
||||||
def option(*param_decls: str, **attrs: t.Any) -> t.Callable[[FC], FC]:
|
|
||||||
"""Attaches an option to the command. All positional arguments are
|
|
||||||
passed as parameter declarations to :class:`Option`; all keyword
|
|
||||||
arguments are forwarded unchanged (except ``cls``).
|
|
||||||
This is equivalent to creating an :class:`Option` instance manually
|
|
||||||
and attaching it to the :attr:`Command.params` list.
|
|
||||||
|
|
||||||
:param cls: the option class to instantiate. This defaults to
|
|
||||||
:class:`Option`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def decorator(f: FC) -> FC:
|
|
||||||
# Issue 926, copy attrs, so pre-defined options can re-use the same cls=
|
|
||||||
option_attrs = attrs.copy()
|
|
||||||
|
|
||||||
if "help" in option_attrs:
|
|
||||||
option_attrs["help"] = inspect.cleandoc(option_attrs["help"])
|
|
||||||
OptionClass = option_attrs.pop("cls", Option)
|
|
||||||
_param_memo(f, OptionClass(param_decls, **option_attrs))
|
|
||||||
return f
|
|
||||||
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
|
|
||||||
def confirmation_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]:
|
|
||||||
"""Add a ``--yes`` option which shows a prompt before continuing if
|
|
||||||
not passed. If the prompt is declined, the program will exit.
|
|
||||||
|
|
||||||
:param param_decls: One or more option names. Defaults to the single
|
|
||||||
value ``"--yes"``.
|
|
||||||
:param kwargs: Extra arguments are passed to :func:`option`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def callback(ctx: Context, param: Parameter, value: bool) -> None:
|
|
||||||
if not value:
|
|
||||||
ctx.abort()
|
|
||||||
|
|
||||||
if not param_decls:
|
|
||||||
param_decls = ("--yes",)
|
|
||||||
|
|
||||||
kwargs.setdefault("is_flag", True)
|
|
||||||
kwargs.setdefault("callback", callback)
|
|
||||||
kwargs.setdefault("expose_value", False)
|
|
||||||
kwargs.setdefault("prompt", "Do you want to continue?")
|
|
||||||
kwargs.setdefault("help", "Confirm the action without prompting.")
|
|
||||||
return option(*param_decls, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
def password_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]:
|
|
||||||
"""Add a ``--password`` option which prompts for a password, hiding
|
|
||||||
input and asking to enter the value again for confirmation.
|
|
||||||
|
|
||||||
:param param_decls: One or more option names. Defaults to the single
|
|
||||||
value ``"--password"``.
|
|
||||||
:param kwargs: Extra arguments are passed to :func:`option`.
|
|
||||||
"""
|
|
||||||
if not param_decls:
|
|
||||||
param_decls = ("--password",)
|
|
||||||
|
|
||||||
kwargs.setdefault("prompt", True)
|
|
||||||
kwargs.setdefault("confirmation_prompt", True)
|
|
||||||
kwargs.setdefault("hide_input", True)
|
|
||||||
return option(*param_decls, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
def version_option(
|
|
||||||
version: t.Optional[str] = None,
|
|
||||||
*param_decls: str,
|
|
||||||
package_name: t.Optional[str] = None,
|
|
||||||
prog_name: t.Optional[str] = None,
|
|
||||||
message: t.Optional[str] = None,
|
|
||||||
**kwargs: t.Any,
|
|
||||||
) -> t.Callable[[FC], FC]:
|
|
||||||
"""Add a ``--version`` option which immediately prints the version
|
|
||||||
number and exits the program.
|
|
||||||
|
|
||||||
If ``version`` is not provided, Click will try to detect it using
|
|
||||||
:func:`importlib.metadata.version` to get the version for the
|
|
||||||
``package_name``. On Python < 3.8, the ``importlib_metadata``
|
|
||||||
backport must be installed.
|
|
||||||
|
|
||||||
If ``package_name`` is not provided, Click will try to detect it by
|
|
||||||
inspecting the stack frames. This will be used to detect the
|
|
||||||
version, so it must match the name of the installed package.
|
|
||||||
|
|
||||||
:param version: The version number to show. If not provided, Click
|
|
||||||
will try to detect it.
|
|
||||||
:param param_decls: One or more option names. Defaults to the single
|
|
||||||
value ``"--version"``.
|
|
||||||
:param package_name: The package name to detect the version from. If
|
|
||||||
not provided, Click will try to detect it.
|
|
||||||
:param prog_name: The name of the CLI to show in the message. If not
|
|
||||||
provided, it will be detected from the command.
|
|
||||||
:param message: The message to show. The values ``%(prog)s``,
|
|
||||||
``%(package)s``, and ``%(version)s`` are available. Defaults to
|
|
||||||
``"%(prog)s, version %(version)s"``.
|
|
||||||
:param kwargs: Extra arguments are passed to :func:`option`.
|
|
||||||
:raise RuntimeError: ``version`` could not be detected.
|
|
||||||
|
|
||||||
.. versionchanged:: 8.0
|
|
||||||
Add the ``package_name`` parameter, and the ``%(package)s``
|
|
||||||
value for messages.
|
|
||||||
|
|
||||||
.. versionchanged:: 8.0
|
|
||||||
Use :mod:`importlib.metadata` instead of ``pkg_resources``. The
|
|
||||||
version is detected based on the package name, not the entry
|
|
||||||
point name. The Python package name must match the installed
|
|
||||||
package name, or be passed with ``package_name=``.
|
|
||||||
"""
|
|
||||||
if message is None:
|
|
||||||
message = _("%(prog)s, version %(version)s")
|
|
||||||
|
|
||||||
if version is None and package_name is None:
|
|
||||||
frame = inspect.currentframe()
|
|
||||||
assert frame is not None
|
|
||||||
assert frame.f_back is not None
|
|
||||||
f_globals = frame.f_back.f_globals if frame is not None else None
|
|
||||||
# break reference cycle
|
|
||||||
# https://docs.python.org/3/library/inspect.html#the-interpreter-stack
|
|
||||||
del frame
|
|
||||||
|
|
||||||
if f_globals is not None:
|
|
||||||
package_name = f_globals.get("__name__")
|
|
||||||
|
|
||||||
if package_name == "__main__":
|
|
||||||
package_name = f_globals.get("__package__")
|
|
||||||
|
|
||||||
if package_name:
|
|
||||||
package_name = package_name.partition(".")[0]
|
|
||||||
|
|
||||||
def callback(ctx: Context, param: Parameter, value: bool) -> None:
|
|
||||||
if not value or ctx.resilient_parsing:
|
|
||||||
return
|
|
||||||
|
|
||||||
nonlocal prog_name
|
|
||||||
nonlocal version
|
|
||||||
|
|
||||||
if prog_name is None:
|
|
||||||
prog_name = ctx.find_root().info_name
|
|
||||||
|
|
||||||
if version is None and package_name is not None:
|
|
||||||
metadata: t.Optional[types.ModuleType]
|
|
||||||
|
|
||||||
try:
|
|
||||||
from importlib import metadata # type: ignore
|
|
||||||
except ImportError:
|
|
||||||
# Python < 3.8
|
|
||||||
import importlib_metadata as metadata # type: ignore
|
|
||||||
|
|
||||||
try:
|
|
||||||
version = metadata.version(package_name) # type: ignore
|
|
||||||
except metadata.PackageNotFoundError: # type: ignore
|
|
||||||
raise RuntimeError(
|
|
||||||
f"{package_name!r} is not installed. Try passing"
|
|
||||||
" 'package_name' instead."
|
|
||||||
)
|
|
||||||
|
|
||||||
if version is None:
|
|
||||||
raise RuntimeError(
|
|
||||||
f"Could not determine the version for {package_name!r} automatically."
|
|
||||||
)
|
|
||||||
|
|
||||||
echo(
|
|
||||||
t.cast(str, message)
|
|
||||||
% {"prog": prog_name, "package": package_name, "version": version},
|
|
||||||
color=ctx.color,
|
|
||||||
)
|
|
||||||
ctx.exit()
|
|
||||||
|
|
||||||
if not param_decls:
|
|
||||||
param_decls = ("--version",)
|
|
||||||
|
|
||||||
kwargs.setdefault("is_flag", True)
|
|
||||||
kwargs.setdefault("expose_value", False)
|
|
||||||
kwargs.setdefault("is_eager", True)
|
|
||||||
kwargs.setdefault("help", _("Show the version and exit."))
|
|
||||||
kwargs["callback"] = callback
|
|
||||||
return option(*param_decls, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
def help_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]:
|
|
||||||
"""Add a ``--help`` option which immediately prints the help page
|
|
||||||
and exits the program.
|
|
||||||
|
|
||||||
This is usually unnecessary, as the ``--help`` option is added to
|
|
||||||
each command automatically unless ``add_help_option=False`` is
|
|
||||||
passed.
|
|
||||||
|
|
||||||
:param param_decls: One or more option names. Defaults to the single
|
|
||||||
value ``"--help"``.
|
|
||||||
:param kwargs: Extra arguments are passed to :func:`option`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def callback(ctx: Context, param: Parameter, value: bool) -> None:
|
|
||||||
if not value or ctx.resilient_parsing:
|
|
||||||
return
|
|
||||||
|
|
||||||
echo(ctx.get_help(), color=ctx.color)
|
|
||||||
ctx.exit()
|
|
||||||
|
|
||||||
if not param_decls:
|
|
||||||
param_decls = ("--help",)
|
|
||||||
|
|
||||||
kwargs.setdefault("is_flag", True)
|
|
||||||
kwargs.setdefault("expose_value", False)
|
|
||||||
kwargs.setdefault("is_eager", True)
|
|
||||||
kwargs.setdefault("help", _("Show this message and exit."))
|
|
||||||
kwargs["callback"] = callback
|
|
||||||
return option(*param_decls, **kwargs)
|
|
||||||
@@ -1,287 +0,0 @@
|
|||||||
import os
|
|
||||||
import typing as t
|
|
||||||
from gettext import gettext as _
|
|
||||||
from gettext import ngettext
|
|
||||||
|
|
||||||
from ._compat import get_text_stderr
|
|
||||||
from .utils import echo
|
|
||||||
|
|
||||||
if t.TYPE_CHECKING:
|
|
||||||
from .core import Context
|
|
||||||
from .core import Parameter
|
|
||||||
|
|
||||||
|
|
||||||
def _join_param_hints(
|
|
||||||
param_hint: t.Optional[t.Union[t.Sequence[str], str]]
|
|
||||||
) -> t.Optional[str]:
|
|
||||||
if param_hint is not None and not isinstance(param_hint, str):
|
|
||||||
return " / ".join(repr(x) for x in param_hint)
|
|
||||||
|
|
||||||
return param_hint
|
|
||||||
|
|
||||||
|
|
||||||
class ClickException(Exception):
|
|
||||||
"""An exception that Click can handle and show to the user."""
|
|
||||||
|
|
||||||
#: The exit code for this exception.
|
|
||||||
exit_code = 1
|
|
||||||
|
|
||||||
def __init__(self, message: str) -> None:
|
|
||||||
super().__init__(message)
|
|
||||||
self.message = message
|
|
||||||
|
|
||||||
def format_message(self) -> str:
|
|
||||||
return self.message
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return self.message
|
|
||||||
|
|
||||||
def show(self, file: t.Optional[t.IO] = None) -> None:
|
|
||||||
if file is None:
|
|
||||||
file = get_text_stderr()
|
|
||||||
|
|
||||||
echo(_("Error: {message}").format(message=self.format_message()), file=file)
|
|
||||||
|
|
||||||
|
|
||||||
class UsageError(ClickException):
|
|
||||||
"""An internal exception that signals a usage error. This typically
|
|
||||||
aborts any further handling.
|
|
||||||
|
|
||||||
:param message: the error message to display.
|
|
||||||
:param ctx: optionally the context that caused this error. Click will
|
|
||||||
fill in the context automatically in some situations.
|
|
||||||
"""
|
|
||||||
|
|
||||||
exit_code = 2
|
|
||||||
|
|
||||||
def __init__(self, message: str, ctx: t.Optional["Context"] = None) -> None:
|
|
||||||
super().__init__(message)
|
|
||||||
self.ctx = ctx
|
|
||||||
self.cmd = self.ctx.command if self.ctx else None
|
|
||||||
|
|
||||||
def show(self, file: t.Optional[t.IO] = None) -> None:
|
|
||||||
if file is None:
|
|
||||||
file = get_text_stderr()
|
|
||||||
color = None
|
|
||||||
hint = ""
|
|
||||||
if (
|
|
||||||
self.ctx is not None
|
|
||||||
and self.ctx.command.get_help_option(self.ctx) is not None
|
|
||||||
):
|
|
||||||
hint = _("Try '{command} {option}' for help.").format(
|
|
||||||
command=self.ctx.command_path, option=self.ctx.help_option_names[0]
|
|
||||||
)
|
|
||||||
hint = f"{hint}\n"
|
|
||||||
if self.ctx is not None:
|
|
||||||
color = self.ctx.color
|
|
||||||
echo(f"{self.ctx.get_usage()}\n{hint}", file=file, color=color)
|
|
||||||
echo(
|
|
||||||
_("Error: {message}").format(message=self.format_message()),
|
|
||||||
file=file,
|
|
||||||
color=color,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class BadParameter(UsageError):
|
|
||||||
"""An exception that formats out a standardized error message for a
|
|
||||||
bad parameter. This is useful when thrown from a callback or type as
|
|
||||||
Click will attach contextual information to it (for instance, which
|
|
||||||
parameter it is).
|
|
||||||
|
|
||||||
.. versionadded:: 2.0
|
|
||||||
|
|
||||||
:param param: the parameter object that caused this error. This can
|
|
||||||
be left out, and Click will attach this info itself
|
|
||||||
if possible.
|
|
||||||
:param param_hint: a string that shows up as parameter name. This
|
|
||||||
can be used as alternative to `param` in cases
|
|
||||||
where custom validation should happen. If it is
|
|
||||||
a string it's used as such, if it's a list then
|
|
||||||
each item is quoted and separated.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
message: str,
|
|
||||||
ctx: t.Optional["Context"] = None,
|
|
||||||
param: t.Optional["Parameter"] = None,
|
|
||||||
param_hint: t.Optional[str] = None,
|
|
||||||
) -> None:
|
|
||||||
super().__init__(message, ctx)
|
|
||||||
self.param = param
|
|
||||||
self.param_hint = param_hint
|
|
||||||
|
|
||||||
def format_message(self) -> str:
|
|
||||||
if self.param_hint is not None:
|
|
||||||
param_hint = self.param_hint
|
|
||||||
elif self.param is not None:
|
|
||||||
param_hint = self.param.get_error_hint(self.ctx) # type: ignore
|
|
||||||
else:
|
|
||||||
return _("Invalid value: {message}").format(message=self.message)
|
|
||||||
|
|
||||||
return _("Invalid value for {param_hint}: {message}").format(
|
|
||||||
param_hint=_join_param_hints(param_hint), message=self.message
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class MissingParameter(BadParameter):
|
|
||||||
"""Raised if click required an option or argument but it was not
|
|
||||||
provided when invoking the script.
|
|
||||||
|
|
||||||
.. versionadded:: 4.0
|
|
||||||
|
|
||||||
:param param_type: a string that indicates the type of the parameter.
|
|
||||||
The default is to inherit the parameter type from
|
|
||||||
the given `param`. Valid values are ``'parameter'``,
|
|
||||||
``'option'`` or ``'argument'``.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
message: t.Optional[str] = None,
|
|
||||||
ctx: t.Optional["Context"] = None,
|
|
||||||
param: t.Optional["Parameter"] = None,
|
|
||||||
param_hint: t.Optional[str] = None,
|
|
||||||
param_type: t.Optional[str] = None,
|
|
||||||
) -> None:
|
|
||||||
super().__init__(message or "", ctx, param, param_hint)
|
|
||||||
self.param_type = param_type
|
|
||||||
|
|
||||||
def format_message(self) -> str:
|
|
||||||
if self.param_hint is not None:
|
|
||||||
param_hint: t.Optional[str] = self.param_hint
|
|
||||||
elif self.param is not None:
|
|
||||||
param_hint = self.param.get_error_hint(self.ctx) # type: ignore
|
|
||||||
else:
|
|
||||||
param_hint = None
|
|
||||||
|
|
||||||
param_hint = _join_param_hints(param_hint)
|
|
||||||
param_hint = f" {param_hint}" if param_hint else ""
|
|
||||||
|
|
||||||
param_type = self.param_type
|
|
||||||
if param_type is None and self.param is not None:
|
|
||||||
param_type = self.param.param_type_name
|
|
||||||
|
|
||||||
msg = self.message
|
|
||||||
if self.param is not None:
|
|
||||||
msg_extra = self.param.type.get_missing_message(self.param)
|
|
||||||
if msg_extra:
|
|
||||||
if msg:
|
|
||||||
msg += f". {msg_extra}"
|
|
||||||
else:
|
|
||||||
msg = msg_extra
|
|
||||||
|
|
||||||
msg = f" {msg}" if msg else ""
|
|
||||||
|
|
||||||
# Translate param_type for known types.
|
|
||||||
if param_type == "argument":
|
|
||||||
missing = _("Missing argument")
|
|
||||||
elif param_type == "option":
|
|
||||||
missing = _("Missing option")
|
|
||||||
elif param_type == "parameter":
|
|
||||||
missing = _("Missing parameter")
|
|
||||||
else:
|
|
||||||
missing = _("Missing {param_type}").format(param_type=param_type)
|
|
||||||
|
|
||||||
return f"{missing}{param_hint}.{msg}"
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
if not self.message:
|
|
||||||
param_name = self.param.name if self.param else None
|
|
||||||
return _("Missing parameter: {param_name}").format(param_name=param_name)
|
|
||||||
else:
|
|
||||||
return self.message
|
|
||||||
|
|
||||||
|
|
||||||
class NoSuchOption(UsageError):
|
|
||||||
"""Raised if click attempted to handle an option that does not
|
|
||||||
exist.
|
|
||||||
|
|
||||||
.. versionadded:: 4.0
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
option_name: str,
|
|
||||||
message: t.Optional[str] = None,
|
|
||||||
possibilities: t.Optional[t.Sequence[str]] = None,
|
|
||||||
ctx: t.Optional["Context"] = None,
|
|
||||||
) -> None:
|
|
||||||
if message is None:
|
|
||||||
message = _("No such option: {name}").format(name=option_name)
|
|
||||||
|
|
||||||
super().__init__(message, ctx)
|
|
||||||
self.option_name = option_name
|
|
||||||
self.possibilities = possibilities
|
|
||||||
|
|
||||||
def format_message(self) -> str:
|
|
||||||
if not self.possibilities:
|
|
||||||
return self.message
|
|
||||||
|
|
||||||
possibility_str = ", ".join(sorted(self.possibilities))
|
|
||||||
suggest = ngettext(
|
|
||||||
"Did you mean {possibility}?",
|
|
||||||
"(Possible options: {possibilities})",
|
|
||||||
len(self.possibilities),
|
|
||||||
).format(possibility=possibility_str, possibilities=possibility_str)
|
|
||||||
return f"{self.message} {suggest}"
|
|
||||||
|
|
||||||
|
|
||||||
class BadOptionUsage(UsageError):
|
|
||||||
"""Raised if an option is generally supplied but the use of the option
|
|
||||||
was incorrect. This is for instance raised if the number of arguments
|
|
||||||
for an option is not correct.
|
|
||||||
|
|
||||||
.. versionadded:: 4.0
|
|
||||||
|
|
||||||
:param option_name: the name of the option being used incorrectly.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self, option_name: str, message: str, ctx: t.Optional["Context"] = None
|
|
||||||
) -> None:
|
|
||||||
super().__init__(message, ctx)
|
|
||||||
self.option_name = option_name
|
|
||||||
|
|
||||||
|
|
||||||
class BadArgumentUsage(UsageError):
|
|
||||||
"""Raised if an argument is generally supplied but the use of the argument
|
|
||||||
was incorrect. This is for instance raised if the number of values
|
|
||||||
for an argument is not correct.
|
|
||||||
|
|
||||||
.. versionadded:: 6.0
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class FileError(ClickException):
|
|
||||||
"""Raised if a file cannot be opened."""
|
|
||||||
|
|
||||||
def __init__(self, filename: str, hint: t.Optional[str] = None) -> None:
|
|
||||||
if hint is None:
|
|
||||||
hint = _("unknown error")
|
|
||||||
|
|
||||||
super().__init__(hint)
|
|
||||||
self.ui_filename = os.fsdecode(filename)
|
|
||||||
self.filename = filename
|
|
||||||
|
|
||||||
def format_message(self) -> str:
|
|
||||||
return _("Could not open file {filename!r}: {message}").format(
|
|
||||||
filename=self.ui_filename, message=self.message
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Abort(RuntimeError):
|
|
||||||
"""An internal signalling exception that signals Click to abort."""
|
|
||||||
|
|
||||||
|
|
||||||
class Exit(RuntimeError):
|
|
||||||
"""An exception that indicates that the application should exit with some
|
|
||||||
status code.
|
|
||||||
|
|
||||||
:param code: the status code to exit with.
|
|
||||||
"""
|
|
||||||
|
|
||||||
__slots__ = ("exit_code",)
|
|
||||||
|
|
||||||
def __init__(self, code: int = 0) -> None:
|
|
||||||
self.exit_code = code
|
|
||||||
@@ -1,301 +0,0 @@
|
|||||||
import typing as t
|
|
||||||
from contextlib import contextmanager
|
|
||||||
from gettext import gettext as _
|
|
||||||
|
|
||||||
from ._compat import term_len
|
|
||||||
from .parser import split_opt
|
|
||||||
|
|
||||||
# Can force a width. This is used by the test system
|
|
||||||
FORCED_WIDTH: t.Optional[int] = None
|
|
||||||
|
|
||||||
|
|
||||||
def measure_table(rows: t.Iterable[t.Tuple[str, str]]) -> t.Tuple[int, ...]:
|
|
||||||
widths: t.Dict[int, int] = {}
|
|
||||||
|
|
||||||
for row in rows:
|
|
||||||
for idx, col in enumerate(row):
|
|
||||||
widths[idx] = max(widths.get(idx, 0), term_len(col))
|
|
||||||
|
|
||||||
return tuple(y for x, y in sorted(widths.items()))
|
|
||||||
|
|
||||||
|
|
||||||
def iter_rows(
|
|
||||||
rows: t.Iterable[t.Tuple[str, str]], col_count: int
|
|
||||||
) -> t.Iterator[t.Tuple[str, ...]]:
|
|
||||||
for row in rows:
|
|
||||||
yield row + ("",) * (col_count - len(row))
|
|
||||||
|
|
||||||
|
|
||||||
def wrap_text(
|
|
||||||
text: str,
|
|
||||||
width: int = 78,
|
|
||||||
initial_indent: str = "",
|
|
||||||
subsequent_indent: str = "",
|
|
||||||
preserve_paragraphs: bool = False,
|
|
||||||
) -> str:
|
|
||||||
"""A helper function that intelligently wraps text. By default, it
|
|
||||||
assumes that it operates on a single paragraph of text but if the
|
|
||||||
`preserve_paragraphs` parameter is provided it will intelligently
|
|
||||||
handle paragraphs (defined by two empty lines).
|
|
||||||
|
|
||||||
If paragraphs are handled, a paragraph can be prefixed with an empty
|
|
||||||
line containing the ``\\b`` character (``\\x08``) to indicate that
|
|
||||||
no rewrapping should happen in that block.
|
|
||||||
|
|
||||||
:param text: the text that should be rewrapped.
|
|
||||||
:param width: the maximum width for the text.
|
|
||||||
:param initial_indent: the initial indent that should be placed on the
|
|
||||||
first line as a string.
|
|
||||||
:param subsequent_indent: the indent string that should be placed on
|
|
||||||
each consecutive line.
|
|
||||||
:param preserve_paragraphs: if this flag is set then the wrapping will
|
|
||||||
intelligently handle paragraphs.
|
|
||||||
"""
|
|
||||||
from ._textwrap import TextWrapper
|
|
||||||
|
|
||||||
text = text.expandtabs()
|
|
||||||
wrapper = TextWrapper(
|
|
||||||
width,
|
|
||||||
initial_indent=initial_indent,
|
|
||||||
subsequent_indent=subsequent_indent,
|
|
||||||
replace_whitespace=False,
|
|
||||||
)
|
|
||||||
if not preserve_paragraphs:
|
|
||||||
return wrapper.fill(text)
|
|
||||||
|
|
||||||
p: t.List[t.Tuple[int, bool, str]] = []
|
|
||||||
buf: t.List[str] = []
|
|
||||||
indent = None
|
|
||||||
|
|
||||||
def _flush_par() -> None:
|
|
||||||
if not buf:
|
|
||||||
return
|
|
||||||
if buf[0].strip() == "\b":
|
|
||||||
p.append((indent or 0, True, "\n".join(buf[1:])))
|
|
||||||
else:
|
|
||||||
p.append((indent or 0, False, " ".join(buf)))
|
|
||||||
del buf[:]
|
|
||||||
|
|
||||||
for line in text.splitlines():
|
|
||||||
if not line:
|
|
||||||
_flush_par()
|
|
||||||
indent = None
|
|
||||||
else:
|
|
||||||
if indent is None:
|
|
||||||
orig_len = term_len(line)
|
|
||||||
line = line.lstrip()
|
|
||||||
indent = orig_len - term_len(line)
|
|
||||||
buf.append(line)
|
|
||||||
_flush_par()
|
|
||||||
|
|
||||||
rv = []
|
|
||||||
for indent, raw, text in p:
|
|
||||||
with wrapper.extra_indent(" " * indent):
|
|
||||||
if raw:
|
|
||||||
rv.append(wrapper.indent_only(text))
|
|
||||||
else:
|
|
||||||
rv.append(wrapper.fill(text))
|
|
||||||
|
|
||||||
return "\n\n".join(rv)
|
|
||||||
|
|
||||||
|
|
||||||
class HelpFormatter:
|
|
||||||
"""This class helps with formatting text-based help pages. It's
|
|
||||||
usually just needed for very special internal cases, but it's also
|
|
||||||
exposed so that developers can write their own fancy outputs.
|
|
||||||
|
|
||||||
At present, it always writes into memory.
|
|
||||||
|
|
||||||
:param indent_increment: the additional increment for each level.
|
|
||||||
:param width: the width for the text. This defaults to the terminal
|
|
||||||
width clamped to a maximum of 78.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
indent_increment: int = 2,
|
|
||||||
width: t.Optional[int] = None,
|
|
||||||
max_width: t.Optional[int] = None,
|
|
||||||
) -> None:
|
|
||||||
import shutil
|
|
||||||
|
|
||||||
self.indent_increment = indent_increment
|
|
||||||
if max_width is None:
|
|
||||||
max_width = 80
|
|
||||||
if width is None:
|
|
||||||
width = FORCED_WIDTH
|
|
||||||
if width is None:
|
|
||||||
width = max(min(shutil.get_terminal_size().columns, max_width) - 2, 50)
|
|
||||||
self.width = width
|
|
||||||
self.current_indent = 0
|
|
||||||
self.buffer: t.List[str] = []
|
|
||||||
|
|
||||||
def write(self, string: str) -> None:
|
|
||||||
"""Writes a unicode string into the internal buffer."""
|
|
||||||
self.buffer.append(string)
|
|
||||||
|
|
||||||
def indent(self) -> None:
|
|
||||||
"""Increases the indentation."""
|
|
||||||
self.current_indent += self.indent_increment
|
|
||||||
|
|
||||||
def dedent(self) -> None:
|
|
||||||
"""Decreases the indentation."""
|
|
||||||
self.current_indent -= self.indent_increment
|
|
||||||
|
|
||||||
def write_usage(
|
|
||||||
self, prog: str, args: str = "", prefix: t.Optional[str] = None
|
|
||||||
) -> None:
|
|
||||||
"""Writes a usage line into the buffer.
|
|
||||||
|
|
||||||
:param prog: the program name.
|
|
||||||
:param args: whitespace separated list of arguments.
|
|
||||||
:param prefix: The prefix for the first line. Defaults to
|
|
||||||
``"Usage: "``.
|
|
||||||
"""
|
|
||||||
if prefix is None:
|
|
||||||
prefix = f"{_('Usage:')} "
|
|
||||||
|
|
||||||
usage_prefix = f"{prefix:>{self.current_indent}}{prog} "
|
|
||||||
text_width = self.width - self.current_indent
|
|
||||||
|
|
||||||
if text_width >= (term_len(usage_prefix) + 20):
|
|
||||||
# The arguments will fit to the right of the prefix.
|
|
||||||
indent = " " * term_len(usage_prefix)
|
|
||||||
self.write(
|
|
||||||
wrap_text(
|
|
||||||
args,
|
|
||||||
text_width,
|
|
||||||
initial_indent=usage_prefix,
|
|
||||||
subsequent_indent=indent,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
# The prefix is too long, put the arguments on the next line.
|
|
||||||
self.write(usage_prefix)
|
|
||||||
self.write("\n")
|
|
||||||
indent = " " * (max(self.current_indent, term_len(prefix)) + 4)
|
|
||||||
self.write(
|
|
||||||
wrap_text(
|
|
||||||
args, text_width, initial_indent=indent, subsequent_indent=indent
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
self.write("\n")
|
|
||||||
|
|
||||||
def write_heading(self, heading: str) -> None:
|
|
||||||
"""Writes a heading into the buffer."""
|
|
||||||
self.write(f"{'':>{self.current_indent}}{heading}:\n")
|
|
||||||
|
|
||||||
def write_paragraph(self) -> None:
|
|
||||||
"""Writes a paragraph into the buffer."""
|
|
||||||
if self.buffer:
|
|
||||||
self.write("\n")
|
|
||||||
|
|
||||||
def write_text(self, text: str) -> None:
|
|
||||||
"""Writes re-indented text into the buffer. This rewraps and
|
|
||||||
preserves paragraphs.
|
|
||||||
"""
|
|
||||||
indent = " " * self.current_indent
|
|
||||||
self.write(
|
|
||||||
wrap_text(
|
|
||||||
text,
|
|
||||||
self.width,
|
|
||||||
initial_indent=indent,
|
|
||||||
subsequent_indent=indent,
|
|
||||||
preserve_paragraphs=True,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
self.write("\n")
|
|
||||||
|
|
||||||
def write_dl(
|
|
||||||
self,
|
|
||||||
rows: t.Sequence[t.Tuple[str, str]],
|
|
||||||
col_max: int = 30,
|
|
||||||
col_spacing: int = 2,
|
|
||||||
) -> None:
|
|
||||||
"""Writes a definition list into the buffer. This is how options
|
|
||||||
and commands are usually formatted.
|
|
||||||
|
|
||||||
:param rows: a list of two item tuples for the terms and values.
|
|
||||||
:param col_max: the maximum width of the first column.
|
|
||||||
:param col_spacing: the number of spaces between the first and
|
|
||||||
second column.
|
|
||||||
"""
|
|
||||||
rows = list(rows)
|
|
||||||
widths = measure_table(rows)
|
|
||||||
if len(widths) != 2:
|
|
||||||
raise TypeError("Expected two columns for definition list")
|
|
||||||
|
|
||||||
first_col = min(widths[0], col_max) + col_spacing
|
|
||||||
|
|
||||||
for first, second in iter_rows(rows, len(widths)):
|
|
||||||
self.write(f"{'':>{self.current_indent}}{first}")
|
|
||||||
if not second:
|
|
||||||
self.write("\n")
|
|
||||||
continue
|
|
||||||
if term_len(first) <= first_col - col_spacing:
|
|
||||||
self.write(" " * (first_col - term_len(first)))
|
|
||||||
else:
|
|
||||||
self.write("\n")
|
|
||||||
self.write(" " * (first_col + self.current_indent))
|
|
||||||
|
|
||||||
text_width = max(self.width - first_col - 2, 10)
|
|
||||||
wrapped_text = wrap_text(second, text_width, preserve_paragraphs=True)
|
|
||||||
lines = wrapped_text.splitlines()
|
|
||||||
|
|
||||||
if lines:
|
|
||||||
self.write(f"{lines[0]}\n")
|
|
||||||
|
|
||||||
for line in lines[1:]:
|
|
||||||
self.write(f"{'':>{first_col + self.current_indent}}{line}\n")
|
|
||||||
else:
|
|
||||||
self.write("\n")
|
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def section(self, name: str) -> t.Iterator[None]:
|
|
||||||
"""Helpful context manager that writes a paragraph, a heading,
|
|
||||||
and the indents.
|
|
||||||
|
|
||||||
:param name: the section name that is written as heading.
|
|
||||||
"""
|
|
||||||
self.write_paragraph()
|
|
||||||
self.write_heading(name)
|
|
||||||
self.indent()
|
|
||||||
try:
|
|
||||||
yield
|
|
||||||
finally:
|
|
||||||
self.dedent()
|
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def indentation(self) -> t.Iterator[None]:
|
|
||||||
"""A context manager that increases the indentation."""
|
|
||||||
self.indent()
|
|
||||||
try:
|
|
||||||
yield
|
|
||||||
finally:
|
|
||||||
self.dedent()
|
|
||||||
|
|
||||||
def getvalue(self) -> str:
|
|
||||||
"""Returns the buffer contents."""
|
|
||||||
return "".join(self.buffer)
|
|
||||||
|
|
||||||
|
|
||||||
def join_options(options: t.Sequence[str]) -> t.Tuple[str, bool]:
|
|
||||||
"""Given a list of option strings this joins them in the most appropriate
|
|
||||||
way and returns them in the form ``(formatted_string,
|
|
||||||
any_prefix_is_slash)`` where the second item in the tuple is a flag that
|
|
||||||
indicates if any of the option prefixes was a slash.
|
|
||||||
"""
|
|
||||||
rv = []
|
|
||||||
any_prefix_is_slash = False
|
|
||||||
|
|
||||||
for opt in options:
|
|
||||||
prefix = split_opt(opt)[0]
|
|
||||||
|
|
||||||
if prefix == "/":
|
|
||||||
any_prefix_is_slash = True
|
|
||||||
|
|
||||||
rv.append((len(prefix), opt))
|
|
||||||
|
|
||||||
rv.sort(key=lambda x: x[0])
|
|
||||||
return ", ".join(x[1] for x in rv), any_prefix_is_slash
|
|
||||||